Merge branch 'bzr/golem' of /Users/distler/Sites/code/instiki

This commit is contained in:
Jacques Distler 2010-04-02 00:02:58 -05:00
commit 3b87094327
17 changed files with 577 additions and 279 deletions

View file

@ -48,7 +48,8 @@ module Sanitizer
svg_attributes = Set.new %w[accent-height accumulate additive alphabetic svg_attributes = Set.new %w[accent-height accumulate additive alphabetic
arabic-form ascent attributeName attributeType baseProfile bbox begin arabic-form ascent attributeName attributeType baseProfile bbox begin
by calcMode cap-height class clip-path clip-rule color color-rendering by calcMode cap-height class clip-path clip-rule color
color-interpolation-filters color-rendering
content cx cy d dx dy descent display dur end fill fill-opacity fill-rule content cx cy d dx dy descent display dur end fill fill-opacity fill-rule
filterRes filterUnits font-family font-size font-stretch font-style filterRes filterUnits font-family font-size font-stretch font-style
font-variant font-weight from fx fy g1 g2 glyph-name gradientUnits font-variant font-weight from fx fy g1 g2 glyph-name gradientUnits

View file

@ -86,6 +86,7 @@ function setupSVGedit(path){
editor.addEventListener("load", function() { editor.addEventListener("load", function() {
editor.svgEditor.setCustomHandlers({ editor.svgEditor.setCustomHandlers({
'save': function(window,svg){ 'save': function(window,svg){
editor.svgEditor.setConfig({no_save_warning: true});
window.opener.postMessage(svg, window.location.protocol + '//' + window.location.host); window.opener.postMessage(svg, window.location.protocol + '//' + window.location.host);
window.close(); window.close();
} }

View file

@ -1,8 +1,9 @@
NAME=svg-edit NAME=svg-edit
VERSION=2.5 VERSION=2.5
MAKEDOCS=naturaldocs/NaturalDocs
PACKAGE=$(NAME)-$(VERSION) PACKAGE=$(NAME)-$(VERSION)
YUI=build/yuicompressor.jar MAKEDOCS=naturaldocs/NaturalDocs
CLOSURE=build/tools/closure-compiler.jar
YUICOMPRESS=build/tools/yuicompressor.jar
ZIP=zip ZIP=zip
all: release firefox opera all: release firefox opera
@ -10,18 +11,21 @@ all: release firefox opera
build/$(PACKAGE): build/$(PACKAGE):
rm -rf config rm -rf config
mkdir config mkdir config
$(MAKEDOCS) -i editor/ -o html docs/ -p config/ -oft -r if [ -x $(MAKEDOCS) ] ; then $(MAKEDOCS) -i editor/ -o html docs/ -p config/ -oft -r ; fi
mkdir -p build/$(PACKAGE) mkdir -p build/$(PACKAGE)
cp -r editor/* build/$(PACKAGE) cp -r editor/* build/$(PACKAGE)
-find build/$(PACKAGE) -name .svn -type d -exec rm -rf {} \; -find build/$(PACKAGE) -name .svn -type d -exec rm -rf {} \;
# minify spin button # minify spin button
java -jar $(YUI) build/$(PACKAGE)/spinbtn/JQuerySpinBtn.js > build/$(PACKAGE)/spinbtn/JQuerySpinBtn.min.js java -jar $(YUICOMPRESS) build/$(PACKAGE)/spinbtn/JQuerySpinBtn.js > build/$(PACKAGE)/spinbtn/JQuerySpinBtn.min.js
java -jar $(CLOSURE) --js build/$(PACKAGE)/spinbtn/JQuerySpinBtn.js --js_output_file build/$(PACKAGE)/spinbtn/JQuerySpinBtn.min-closure.js
# minify SVG-edit files # minify SVG-edit files
java -jar $(YUI) build/$(PACKAGE)/svg-editor.js > build/$(PACKAGE)/svg-editor.min.js java -jar $(YUICOMPRESS) build/$(PACKAGE)/svg-editor.js > build/$(PACKAGE)/svg-editor.min.js
java -jar $(YUI) build/$(PACKAGE)/svgcanvas.js > build/$(PACKAGE)/svgcanvas.min.js java -jar $(CLOSURE) --js build/$(PACKAGE)/svg-editor.js --js_output_file build/$(PACKAGE)/svg-editor.min-closure.js
java -jar $(YUICOMPRESS) build/$(PACKAGE)/svgcanvas.js > build/$(PACKAGE)/svgcanvas.min.js
java -jar $(CLOSURE) --js build/$(PACKAGE)/svgcanvas.js --js_output_file build/$(PACKAGE)/svgcanvas.min-closure.js
# CSS files do not work remotely # CSS files do not work remotely
# java -jar $(YUI) build/$(PACKAGE)/spinbtn/JQuerySpinBtn.css > build/$(PACKAGE)/spinbtn/JQuerySpinBtn.min.css # java -jar $(YUICOMPRESS) build/$(PACKAGE)/spinbtn/JQuerySpinBtn.css > build/$(PACKAGE)/spinbtn/JQuerySpinBtn.min.css
# java -jar $(YUI) build/$(PACKAGE)/svg-editor.css > build/$(PACKAGE)/svg-editor.min.css # java -jar $(YUICOMPRESS) build/$(PACKAGE)/svg-editor.css > build/$(PACKAGE)/svg-editor.min.css
release: build/$(PACKAGE) release: build/$(PACKAGE)
cd build ; $(ZIP) $(PACKAGE).zip -r $(PACKAGE) ; cd .. cd build ; $(ZIP) $(PACKAGE).zip -r $(PACKAGE) ; cd ..

Binary file not shown.

View file

@ -0,0 +1,90 @@
/*
* ext-eyedropper.js
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Jeff Schiller
*
*/
svgEditor.addExtension("eyedropper", function(S) {
var svgcontent = S.svgcontent,
svgns = "http://www.w3.org/2000/svg",
svgdoc = S.svgroot.parentNode.ownerDocument,
ChangeElementCommand = svgCanvas.getPrivateMethods().ChangeElementCommand,
addToHistory = svgCanvas.getPrivateMethods().addCommandToHistory,
currentStyle = {fillPaint: "red", fillOpacity: 1.0,
strokePaint: "black", strokeOpacity: 1.0,
strokeWidth: 5, strokeDashArray: null,
opacity: 1.0 };
return {
name: "eyedropper",
svgicons: "extensions/eyedropper-icon.xml",
buttons: [{
id: "tool_eyedropper",
type: "mode",
title: "Eye Dropper Tool",
events: {
"click": function() {
svgCanvas.setMode("eyedropper");
}
}
}],
// if we have selected an element, grab its paint and enable the eye dropper button
selectedChanged: function(opts) {
// if we are in eyedropper mode, we don't want to disable the eye-dropper tool
var mode = svgCanvas.getMode();
if (mode == "eyedropper") return;
var elem = null;
var tool = $('#tool_eyedropper');
// enable-eye-dropper if one element is selected
if (opts.elems.length == 1 && opts.elems[0] &&
$.inArray(opts.elems[0].nodeName, ['svg', 'g', 'use']) == -1)
{
elem = opts.elems[0];
tool.removeClass('disabled');
// grab the current style
currentStyle.fillPaint = elem.getAttribute("fill") || "black";
currentStyle.fillOpacity = elem.getAttribute("fill-opacity") || 1.0;
currentStyle.strokePaint = elem.getAttribute("stroke");
currentStyle.strokeOpacity = elem.getAttribute("stroke-opacity") || 1.0;
currentStyle.strokeWidth = elem.getAttribute("stroke-width");
currentStyle.strokeDashArray = elem.getAttribute("stroke-dasharray");
currentStyle.opacity = elem.getAttribute("opacity") || 1.0;
}
// disable eye-dropper tool
else {
tool.addClass('disabled');
}
},
mouseDown: function(opts) {
var mode = svgCanvas.getMode();
if (mode == "eyedropper") {
var e = opts.event;
var target = e.target;
if ($.inArray(target.nodeName, ['svg', 'g', 'use']) == -1) {
var changes = {};
var change = function(elem, attrname, newvalue) {
changes[attrname] = elem.getAttribute(attrname);
elem.setAttribute(attrname, newvalue);
};
if (currentStyle.fillPaint) change(target, "fill", currentStyle.fillPaint);
if (currentStyle.fillOpacity) change(target, "fill-opacity", currentStyle.fillOpacity);
if (currentStyle.strokePaint) change(target, "stroke", currentStyle.strokePaint);
if (currentStyle.strokeOpacity) change(target, "stroke-opacity", currentStyle.strokeOpacity);
if (currentStyle.strokeWidth) change(target, "stroke-width", currentStyle.strokeWidth);
if (currentStyle.strokeDashArray) change(target, "stroke-dasharray", currentStyle.strokeDashArray);
if (currentStyle.opacity) change(target, "opacity", currentStyle.opacity);
addToHistory(new ChangeElementCommand(target, changes));
}
}
},
};
});

View file

@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg">
<g id="tool_eyedropper">
<svg viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<defs>
<radialGradient id="eyedropper_svg_6" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
<stop offset="1" stop-color="#e5e5e5" stop-opacity="0.38"/>
</radialGradient>
<linearGradient id="eyedropper_svg_15" x1="0" y1="0" x2="0.58594" y2="0.55078">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.57"/>
<stop offset="1" stop-color="#000056" stop-opacity="1"/>
</linearGradient>
<linearGradient id="eyedropper_svg_19" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
</linearGradient>
</defs>
<g display="inline">
<title>Layer 1</title>
<path d="m193.899994,73l-119.899979,118l-15,39.5l10.25,4.5l43.750015,-20l108.999969,-112l-28.100006,-30z" id="svg_3" fill="none" stroke="#000000" stroke-width="5"/>
<path d="m58.649994,232c-2.75,28.200012 -26.399994,28.950012 -21.899994,59c4.5,30.049988 55,28 55.5,-1.25c0.5,-29.25 -20.25,-28.75 -22.25,-54.75l-11.350006,-3z" id="svg_4" fill="#aa56ff" stroke="#000000" stroke-width="7"/>
<path d="m45.474976,269.275024l13.775024,0.474976l-0.75,16.75l-14.25,-1.25l1.224976,-15.974976z" id="svg_5" fill="url(#eyedropper_svg_6)" stroke-width="5" fill-opacity="0.73"/>
<path d="m217.899994,46c21.5,-101.549999 141.600006,20.449997 28.100006,33l-5,44l-63,-66l39.899994,-11z" id="svg_2" fill="#000000" stroke-width="5"/>
<path d="m206.825012,61.075008c3.712494,-2.46249 7.637482,-3.53751 14.424988,-5.575008c10.125,-16.5 32.875,-41.5 40.5,-35c7.625,6.5 -21.25,35.625 -37.5,39.25c-5.5,10.125 -8,13.875 -17.25,16.5c-2.837494,-8.162514 -4.262482,-12.337486 -0.174988,-15.174992z" id="svg_7" fill="url(#eyedropper_svg_15)" stroke-width="5"/>
<path d="m133.049988,134.75l46.950012,9.25l-66,70l-42.5,20.5l-11.5,-5l14,-37.5l59.049988,-57.25z" id="svg_11" fill="#aa56ff" stroke="#000000" stroke-width="7"/>
<path d="m71.425034,212.350006l9.050888,-20.022537l51.516724,-49.327469l8.507355,0.97197l-69.074966,68.378036z" id="svg_16" fill="url(#eyedropper_svg_19)" stroke-width="5"/>
</g>
</svg>
</g>
<g id="svg_eof"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

View file

@ -5,11 +5,10 @@
in the extension. The SVG inside the group makes up the actual icon, and in the extension. The SVG inside the group makes up the actual icon, and
needs use a viewBox instead of width/height for it to scale properly. needs use a viewBox instead of width/height for it to scale properly.
Multiple icons can be included, each within their own group. A final element Multiple icons can be included, each within their own group.
with ID "svg_eof" must be included too.
--> -->
<g id="hello_world"> <g id="hello_world">
<svg viewBox="0 0 102 102" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="102" height="102" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ --> <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<g> <g>
<title>Layer 1</title> <title>Layer 1</title>
@ -19,5 +18,4 @@
</g> </g>
</svg> </svg>
</g> </g>
<g id="svg_eof"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,47 +1,32 @@
#About #About
**jQuery.hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. **jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.
It is based on a library [Shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/shortcut.js) written by [Binny V A](http://www.openjs.com/). This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](http://github.com/tzuryby/hotkeys)
The syntax is as follows: The syntax is as follows:
<pre>
$(expression).bind(<types>,<options>, <handler>);
$(expression).unbind(<types>,<options>, <handler>);
$(document).bind('keydown', 'Ctrl+a', fn); $(expression).bind(types, keys, handler);
$(expression).unbind(types, handler);
// e.g. replace '$' sign with 'EUR'
$('input.foo').bind('keyup', '$', function(){ $(document).bind('keydown', 'ctrl+a', fn);
this.value = this.value.replace('$', 'EUR');
}); // e.g. replace '$' sign with 'EUR'
$('input.foo').bind('keyup', '$', function(){
$('div.foo').unbind('keydown', 'Ctrl+a', fn); this.value = this.value.replace('$', 'EUR');
</pre> });
## [Live Demo](http://jshotkeys.googlepages.com/test-static-01.html)
## Types ## Types
Supported types are `'keydown'`, `'keyup'` and `'keypress'` Supported types are `'keydown'`, `'keyup'` and `'keypress'`
## Options ## Notes
The options are `'combi'` i.e. the key combination, and `'disableInInput'` which allow your code not to be executed when the cursor is located inside an input ( `$(elem).is('input') || $(elem).is('textarea')` ).
As you can see, the key combination can be passed as string or as an object. You may pass an object in case you wish to override the default option for `disableInInput` which is set to `false`:
<pre>
$(document).bind('keydown', {combi:'a', disableinInput: true}, fn);
</pre>
I.e. when cursor is within an input field, `'a'` will be inserted into the input field without interfering.
If you want to use more than one modifiers (e.g. alt+ctrl+z) you should define them by an alphabetical order e.g. alt+ctrl+shift If you want to use more than one modifiers (e.g. alt+ctrl+z) you should define them by an alphabetical order e.g. alt+ctrl+shift
Modifiers are case insensitive, i.e. 'Ctrl+a' 'ctrl+a'. Hotkeys aren't tracked if you're inside of an input element (unless you explicitly bind the hotkey directly to the input). This helps to avoid conflict with normal user typing.
## Handler
In previous versions there was an option propagate which is removed now and implemented at the user code level.
When using jQuery, if an event handler returns false, jQuery will call `stopPropagation()` and `preventDefault()`
## jQuery Compatibility ## jQuery Compatibility
Tested with *jQuery 1.2.6*
Works with jQuery 1.4.2 and newer.
It known to be working with all the major browsers on all available platforms (Win/Mac/Linux) It known to be working with all the major browsers on all available platforms (Win/Mac/Linux)
@ -51,43 +36,10 @@ It known to be working with all the major browsers on all available platforms (W
* Safari-3 * Safari-3
* Chrome-0.2 * Chrome-0.2
## Features added in this version (0.7.x) ### Addendum
* Implemented as $.fn - let you use `this`.
* jQuery selectors are supported.
* Extending `$.fn.bind` and `$.fn.unbind` so you get a single interface for binding events to handlers
## Overriding jQuery
The plugin wraps the following jQuery methods:
* $.fn.bind
* $.fn.unbind
* $.find
Even though the plugin overrides these methods, the original methods will *always* be called.
The plugin will add functionality only for the `keydown`, `keyup` and `keypress` event types. Any other types are passed untouched to the original `'bind()'` and `'unbind()'` methods.
Moreover, if you call `bind()` without passing the shortcut key combination e.g. `$(document).bind('keydown', fn)` only the original `'bind()'` method will be executed.
I also modified the `$.fn.find` method by adding a single line at the top of the function body. here is the code:
<pre>
jQuery.fn.find = function( selector ) {
// the line I added
this.query=selector;
// call jQuery original find
return jQuery.fn.__find__.apply(this, arguments);
};
</pre>
You can read about this at [jQuery's User Group](http://groups.google.com/group/jquery-en/browse_thread/thread/18f9825e8d22f18d)
###Notes
Firefox is the most liberal one in the manner of letting you capture all short-cuts even those that are built-in in the browser such as `Ctrl-t` for new tab, or `Ctrl-a` for selecting all text. You can always bubble them up to the browser by returning `true` in your handler. Firefox is the most liberal one in the manner of letting you capture all short-cuts even those that are built-in in the browser such as `Ctrl-t` for new tab, or `Ctrl-a` for selecting all text. You can always bubble them up to the browser by returning `true` in your handler.
Others, (IE) either let you handle built-in short-cuts, but will add their functionality after your code has executed. Or (Opera/Safari) will *not* pass those events to the DOM at all. Others, (IE) either let you handle built-in short-cuts, but will add their functionality after your code has executed. Or (Opera/Safari) will *not* pass those events to the DOM at all.
*So, if you bind `Ctrl-Q` or `Alt-F4` and your Safari/Opera window is closed don't be surprised.* *So, if you bind `Ctrl-Q` or `Alt-F4` and your Safari/Opera window is closed don't be surprised.*
###Current Version is: beta 0.7

View file

@ -1,19 +1,15 @@
(function(jQuery){jQuery.fn.__bind__=jQuery.fn.bind;jQuery.fn.__unbind__=jQuery.fn.unbind;jQuery.fn.__find__=jQuery.fn.find;var hotkeys={version:'0.7.9',override:/keypress|keydown|keyup/g,triggersMap:{},specialKeys:{27:'esc',9:'tab',32:'space',13:'return',8:'backspace',145:'scroll',20:'capslock',144:'numlock',19:'pause',45:'insert',36:'home',46:'del',35:'end',33:'pageup',34:'pagedown',37:'left',38:'up',39:'right',40:'down',109:'-',112:'f1',113:'f2',114:'f3',115:'f4',116:'f5',117:'f6',118:'f7',119:'f8',120:'f9',121:'f10',122:'f11',123:'f12',191:'/'},shiftNums:{"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"_","=":"+",";":":","'":"\"",",":"<",".":">","/":"?","\\":"|"},newTrigger:function(type,combi,callback){var result={};result[type]={};result[type][combi]={cb:callback,disableInInput:false};return result;}};hotkeys.specialKeys=jQuery.extend(hotkeys.specialKeys,{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:'/'});jQuery.fn.find=function(selector){this.query=selector;return jQuery.fn.__find__.apply(this,arguments);};jQuery.fn.unbind=function(type,combi,fn){if(jQuery.isFunction(combi)){fn=combi;combi=null;} /*
if(combi&&typeof combi==='string'){var selectorId=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();var hkTypes=type.split(' ');for(var x=0;x<hkTypes.length;x++){delete hotkeys.triggersMap[selectorId][hkTypes[x]][combi];}} * jQuery Hotkeys Plugin
return this.__unbind__(type,fn);};jQuery.fn.bind=function(type,data,fn){var handle=type.match(hotkeys.override);if(jQuery.isFunction(data)||!handle){return this.__bind__(type,data,fn);} * Copyright 2010, John Resig
else{var result=null,pass2jq=jQuery.trim(type.replace(hotkeys.override,''));if(pass2jq){result=this.__bind__(pass2jq,data,fn);} * Dual licensed under the MIT or GPL Version 2 licenses.
if(typeof data==="string"){data={'combi':data};} *
if(data.combi){for(var x=0;x<handle.length;x++){var eventType=handle[x];var combi=data.combi.toLowerCase(),trigger=hotkeys.newTrigger(eventType,combi,fn),selectorId=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();trigger[eventType][combi].disableInInput=data.disableInInput;if(!hotkeys.triggersMap[selectorId]){hotkeys.triggersMap[selectorId]=trigger;} * http://github.com/jeresig/jquery.hotkeys
else if(!hotkeys.triggersMap[selectorId][eventType]){hotkeys.triggersMap[selectorId][eventType]=trigger[eventType];} *
var mapPoint=hotkeys.triggersMap[selectorId][eventType][combi];if(!mapPoint){hotkeys.triggersMap[selectorId][eventType][combi]=[trigger[eventType][combi]];} * Based upon the plugin by Tzury Bar Yochay:
else if(mapPoint.constructor!==Array){hotkeys.triggersMap[selectorId][eventType][combi]=[mapPoint];} * http://github.com/tzuryby/hotkeys
else{hotkeys.triggersMap[selectorId][eventType][combi][mapPoint.length]=trigger[eventType][combi];} *
this.each(function(){var jqElem=jQuery(this);if(jqElem.attr('hkId')&&jqElem.attr('hkId')!==selectorId){selectorId=jqElem.attr('hkId')+";"+selectorId;} * Original idea by:
jqElem.attr('hkId',selectorId);});result=this.__bind__(handle.join(' '),data,hotkeys.handler)}} * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
return result;}};hotkeys.findElement=function(elem){if(!jQuery(elem).attr('hkId')){if(jQuery.browser.opera||jQuery.browser.safari){while(!jQuery(elem).attr('hkId')&&elem.parentNode){elem=elem.parentNode;}}} */
return elem;};hotkeys.handler=function(event){var target=hotkeys.findElement(event.currentTarget),jTarget=jQuery(target),ids=jTarget.attr('hkId');if(ids){ids=ids.split(';');var code=event.which,type=event.type,special=hotkeys.specialKeys[code],character=!special&&String.fromCharCode(code).toLowerCase(),shift=event.shiftKey,ctrl=event.ctrlKey,alt=event.altKey||event.originalEvent.altKey,mapPoint=null;for(var x=0;x<ids.length;x++){if(hotkeys.triggersMap[ids[x]][type]){mapPoint=hotkeys.triggersMap[ids[x]][type];break;}}
if(mapPoint){var trigger;if(!shift&&!ctrl&&!alt){trigger=mapPoint[special]||(character&&mapPoint[character]);} (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<f;j++){if(g[e[j]]){return c.apply(this,arguments)}}}}b.each(["keydown","keyup","keypress"],function(){b.event.special[this]={add:a}})})(jQuery);
else{var modif='';if(alt)modif+='alt+';if(ctrl)modif+='ctrl+';if(shift)modif+='shift+';trigger=mapPoint[modif+special];if(!trigger){if(character){trigger=mapPoint[modif+character]||mapPoint[modif+hotkeys.shiftNums[character]]||(modif==='shift+'&&mapPoint[hotkeys.shiftNums[character]]);}}}
if(trigger){var result=false;for(var x=0;x<trigger.length;x++){if(trigger[x].disableInInput){var elem=jQuery(event.target);if(jTarget.is("input")||jTarget.is("textarea")||jTarget.is("select")||elem.is("input")||elem.is("textarea")||elem.is("select")){return true;}}
result=result||trigger[x].cb.apply(this,[event]);}
return result;}}}};window.hotkeys=hotkeys;return jQuery;})(jQuery);

View file

@ -383,6 +383,7 @@ body {
top: 75px; top: 75px;
left: 0; left: 0;
padding-left: 2px; padding-left: 2px;
background: #D8D8D8; /* Needed so flyout icons don't appear on the left */
z-index: 4; z-index: 4;
} }
@ -466,17 +467,13 @@ body {
} }
.magic_field > * {
float: left;
}
span.zoom_tool { span.zoom_tool {
line-height: 26px; line-height: 26px;
padding: 3px; padding: 3px;
} }
.magic_field input { #zoom_panel label {
margin-top: 5px; margin-top: 2px;
} }
.dropdown { .dropdown {
@ -701,6 +698,21 @@ span.zoom_tool {
height: 26px; height: 26px;
} }
#toggle_stroke_tools {
letter-spacing: -.2em;
padding-right: 8px;
}
#toggle_stroke_tools:hover {
cursor: pointer;
background: #FFC;
}
.stroke_tool {
display: none;
white-space: nowrap;
}
.color_tool > *:first-child { .color_tool > *:first-child {
-moz-border-radius-topleft: 4px; -moz-border-radius-topleft: 4px;
-moz-border-radius-bottomleft: 4px; -moz-border-radius-bottomleft: 4px;
@ -739,6 +751,8 @@ span.zoom_tool {
#svg_editor #tools_bottom_3 { #svg_editor #tools_bottom_3 {
// position: relative;
z-index: 2;
} }
#svg_editor #copyright { #svg_editor #copyright {
@ -1023,4 +1037,4 @@ span.zoom_tool {
-webkit-border-radius: 0px; -webkit-border-radius: 0px;
} }
foreignObject {line-height:1.0} foreignObject {line-height:1.0}

View file

@ -19,6 +19,8 @@
<script type="text/javascript" src="svgcanvas.js"></script> <script type="text/javascript" src="svgcanvas.js"></script>
<script type="text/javascript" src="svg-editor.js"></script> <script type="text/javascript" src="svg-editor.js"></script>
<script type="text/javascript" src="locale/locale.js"></script> <script type="text/javascript" src="locale/locale.js"></script>
<!-- you can load extensions here -->
<!-- <script type="text/javascript" src="extensions/ext-helloworld.js"></script> -->
<!-- Release version of script tags: > <!-- Release version of script tags: >
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
@ -340,6 +342,7 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<label id="tool_node_y">y: <label id="tool_node_y">y:
<input id="path_node_y" class="attr_changer" title="Change node's y coordinate" size="3" data-attr="y"/> <input id="path_node_y" class="attr_changer" title="Change node's y coordinate" size="3" data-attr="y"/>
</label> </label>
<select id="seg_type" title="Change Segment type"> <select id="seg_type" title="Change Segment type">
<option id="straight_segments" selected="selected" value="4">Straight</option> <option id="straight_segments" selected="selected" value="4">Straight</option>
<option id="curve_segments" value="6">Curve</option> <option id="curve_segments" value="6">Curve</option>
@ -380,9 +383,11 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<div id="tools_bottom" class="tools_panel"> <div id="tools_bottom" class="tools_panel">
<!-- Zoom buttons --> <!-- Zoom buttons -->
<div id="zoom_panel" class="magic_field"> <div id="zoom_panel" class="toolset">
<span id="zoomLabel" class="zoom_tool label">zoom:</span> <label>
<span id="zoomLabel" class="zoom_tool">zoom:</span>
<input id="zoom" title="Change zoom level" size="3" value="100" type="text" /> <input id="zoom" title="Change zoom level" size="3" value="100" type="text" />
</label>
<div id="zoom_dropdown" class="dropdown"> <div id="zoom_dropdown" class="dropdown">
<button></button> <button></button>
<ul> <ul>
@ -427,7 +432,7 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<input id="stroke_width" title="Change stroke width by 1, shift-click to change by 0.1" size="2" value="5" type="text" data-attr="Stroke Width"/> <input id="stroke_width" title="Change stroke width by 1, shift-click to change by 0.1" size="2" value="5" type="text" data-attr="Stroke Width"/>
</label> </label>
<label> <label class="stroke_tool">
<select id="stroke_style" title="Change stroke dash style"> <select id="stroke_style" title="Change stroke dash style">
<option selected="selected" value="none">&mdash;</option> <option selected="selected" value="none">&mdash;</option>
<option value="2,2">...</option> <option value="2,2">...</option>
@ -435,7 +440,34 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<option value="5,2,2,2">- .</option> <option value="5,2,2,2">- .</option>
<option value="5,2,2,2,2,2">- ..</option> <option value="5,2,2,2,2,2">- ..</option>
</select> </select>
</label>
<!-- TODO: Turn these into icon lists, rather than text ones -->
<label class="stroke_tool">
<span>Joins:</span>
<select id="stroke_linejoin" title="Change Linejoin type">
<option id="linejoin_miter" selected="selected" value="miter">Miter</option>
<option id="linejoin_round" value="round">Round</option>
<option id="linejoin_bevel" value="bevel">Bevel</option>
</select>
</label> </label>
<label class="stroke_tool">
<span>Caps:</span>
<select id="stroke_linecap" title="Change Linecap type">
<option id="linecap_butt" selected="selected" value="butt">Butt</option>
<option id="linecap_round" value="round">Round</option>
<option id="linecap_square" value="square">Square</option>
</select>
</label>
<div id="toggle_stroke_tools" title="Show/hide more stroke tools">
&gt;&gt;
</div>
</div> </div>
</div> </div>

View file

@ -45,7 +45,7 @@
imgPath: 'images/', imgPath: 'images/',
langPath: 'locale/', langPath: 'locale/',
extPath: 'extensions/', extPath: 'extensions/',
extensions: ['ext-arrows.js', 'ext-connector.js', 'ext-itex.js'], extensions: ['ext-arrows.js', 'ext-connector.js', 'ext-eyedropper.js', 'ext-itex.js'],
initTool: 'select', initTool: 'select',
wireframe: false wireframe: false
}, },
@ -59,6 +59,7 @@
'layerHasThatName':"Layer already has that name", 'layerHasThatName':"Layer already has that name",
'QmoveElemsToLayer':"Move selected elements to layer '%s'?", 'QmoveElemsToLayer':"Move selected elements to layer '%s'?",
'QwantToClear':'Do you want to clear the drawing?\nThis will also erase your undo history!', 'QwantToClear':'Do you want to clear the drawing?\nThis will also erase your undo history!',
'QwantToOpen':'Do you want to open a new file?\nThis will also erase your undo history!',
'QerrorsRevertToSource':'There were parsing errors in your SVG source.\nRevert back to original SVG source?', 'QerrorsRevertToSource':'There were parsing errors in your SVG source.\nRevert back to original SVG source?',
'QignoreSourceChanges':'Ignore changes made to SVG source?', 'QignoreSourceChanges':'Ignore changes made to SVG source?',
'featNotSupported':'Feature not supported', 'featNotSupported':'Feature not supported',
@ -143,6 +144,7 @@
svgCanvas.open = opts.open; svgCanvas.open = opts.open;
} }
if(opts.save) { if(opts.save) {
show_save_warning = false;
svgCanvas.bind("saved", opts.save); svgCanvas.bind("saved", opts.save);
} }
} }
@ -190,10 +192,19 @@
} }
})(); })();
var extFunc = function() {
$.each(curConfig.extensions, function() {
$.getScript(curConfig.extPath + this);
});
}
// Load extensions // Load extensions
$.each(curConfig.extensions, function() { // Bit of a hack to run extensions in local Opera
$.getScript(curConfig.extPath + this); if(window.opera && document.location.protocol === 'file:') {
}); setTimeout(extFunc, 1000);
} else {
extFunc();
}
$.svgIcons(curConfig.imgPath + 'svg_edit_icons.svg', { $.svgIcons(curConfig.imgPath + 'svg_edit_icons.svg', {
w:24, h:24, w:24, h:24,
@ -361,7 +372,8 @@
modKey = "", //(isMac ? "meta+" : "ctrl+"); modKey = "", //(isMac ? "meta+" : "ctrl+");
path = svgCanvas.pathActions, path = svgCanvas.pathActions,
default_img_url = curConfig.imgPath + "logo.png", default_img_url = curConfig.imgPath + "logo.png",
workarea = $("#workarea"); workarea = $("#workarea"),
show_save_warning = false;
// This sets up alternative dialog boxes. They mostly work the same way as // This sets up alternative dialog boxes. They mostly work the same way as
// their UI counterparts, expect instead of returning the result, a callback // their UI counterparts, expect instead of returning the result, a callback
@ -388,7 +400,7 @@
if(type == 'prompt') { if(type == 'prompt') {
var input = $('<input type="text">').prependTo(btn_holder); var input = $('<input type="text">').prependTo(btn_holder);
input.val(defText || ''); input.val(defText || '');
input.bind('keydown', {combi:'return'}, function() {ok.click();}); input.bind('keydown', 'return', function() {ok.click();});
} }
box.show(); box.show();
@ -445,6 +457,8 @@
var strokePaint = new $.jGraduate.Paint({solidColor: curConfig.initStroke.color}); var strokePaint = new $.jGraduate.Paint({solidColor: curConfig.initStroke.color});
var saveHandler = function(window,svg) { var saveHandler = function(window,svg) {
show_save_warning = false;
// by default, we add the XML prolog back, systems integrating SVG-edit (wikis, CMSs) // by default, we add the XML prolog back, systems integrating SVG-edit (wikis, CMSs)
// can just provide their own custom save handler and might not want the XML prolog // can just provide their own custom save handler and might not want the XML prolog
svg = "<?xml version='1.0'?>\n" + svg; svg = "<?xml version='1.0'?>\n" + svg;
@ -525,6 +539,8 @@
selectedElement = elem; selectedElement = elem;
} }
} }
show_save_warning = true;
// we update the contextual panel with potentially new // we update the contextual panel with potentially new
// positional/sizing information (we DON'T want to update the // positional/sizing information (we DON'T want to update the
@ -607,7 +623,7 @@
$(this).mouseup(func); $(this).mouseup(func);
if(opts.key) { if(opts.key) {
$(document).bind('keydown', {combi: opts.key+'', disableInInput:true}, func); $(document).bind('keydown', opts.key+'', func);
} }
}); });
@ -878,7 +894,7 @@
}); });
} }
if(btn.key) { if(btn.key) {
$(document).bind('keydown', {combi: btn.key, disableInInput: true}, func); $(document).bind('keydown', btn.key, func);
if(btn.title) button.attr("title", btn.title + ' ['+btn.key+']'); if(btn.title) button.attr("title", btn.title + ' ['+btn.key+']');
} }
} else { } else {
@ -999,6 +1015,8 @@
$('#stroke_width').val(selectedElement.getAttribute("stroke-width")||1); $('#stroke_width').val(selectedElement.getAttribute("stroke-width")||1);
$('#stroke_style').val(selectedElement.getAttribute("stroke-dasharray")||"none"); $('#stroke_style').val(selectedElement.getAttribute("stroke-dasharray")||"none");
$('#stroke_linejoin').val(selectedElement.getAttribute("stroke-linejoin")||"miter");
$('#stroke_linecap').val(selectedElement.getAttribute("stroke-linecap")||"butt");
} }
// All elements including image and group have opacity // All elements including image and group have opacity
@ -1270,9 +1288,20 @@
} }
$('#stroke_style').change(function(){ $('#stroke_style').change(function(){
svgCanvas.setStrokeStyle(this.options[this.selectedIndex].value); svgCanvas.setStrokeAttr('stroke-dasharray', $(this).val());
operaRepaint(); operaRepaint();
}); });
$('#stroke_linejoin').change(function(){
svgCanvas.setStrokeAttr('stroke-linejoin', $(this).val());
operaRepaint();
});
$('#stroke_linecap').change(function(){
svgCanvas.setStrokeAttr('stroke-linecap', $(this).val());
operaRepaint();
});
// Lose focus for select elements when changed (Allows keyboard shortcuts to work better) // Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
$('select').change(function(){$(this).blur();}); $('select').change(function(){$(this).blur();});
@ -1383,6 +1412,14 @@
updateToolButtonState(); updateToolButtonState();
}); });
$("#toggle_stroke_tools").toggle(function() {
$(".stroke_tool").css('display','table-cell');
$(this).text('<<');
}, function() {
$(".stroke_tool").css('display','none');
$(this).text('>>');
});
// This is a common function used when a tool has been clicked (chosen) // This is a common function used when a tool has been clicked (chosen)
// It does several common things: // It does several common things:
// - removes the tool_button_current class from whatever tool currently has it // - removes the tool_button_current class from whatever tool currently has it
@ -1401,6 +1438,44 @@
return true; return true;
}; };
(function() {
var last_x = null, last_y = null, w_area = workarea[0],
panning = false, keypan = false;
$('#svgcanvas').bind('mousemove mouseup', function(evt) {
if(panning === false) return;
w_area.scrollLeft -= (evt.clientX - last_x);
w_area.scrollTop -= (evt.clientY - last_y);
last_x = evt.clientX;
last_y = evt.clientY;
if(evt.type === 'mouseup') panning = false;
return false;
}).mousedown(function(evt) {
if(evt.button === 1 || keypan === true) {
panning = true;
last_x = evt.clientX;
last_y = evt.clientY;
return false;
}
});
$(window).mouseup(function() {
panning = false;
});
$(document).bind('keydown', 'space', function(evt) {
svgCanvas.spaceKey = keypan = true;
evt.preventDefault();
}).bind('keyup', 'space', function(evt) {
evt.preventDefault();
svgCanvas.spaceKey = keypan = false;
});
}());
(function() { (function() {
var button = $('#main_icon'); var button = $('#main_icon');
var overlay = $('#main_icon span'); var overlay = $('#main_icon span');
@ -2022,6 +2097,7 @@
".tool_button,\ ".tool_button,\
.push_button,\ .push_button,\
.tool_button_current,\ .tool_button_current,\
.push_button_pressed,\
.disabled,\ .disabled,\
.tools_flyout .tool_button": { .tools_flyout .tool_button": {
'width': {s: '16px', l: '32px', xl: '48px'}, 'width': {s: '16px', l: '32px', xl: '48px'},
@ -2047,11 +2123,11 @@
"div#workarea": { "div#workarea": {
'left': {s: '27px', l: '46px', xl: '65px'}, 'left': {s: '27px', l: '46px', xl: '65px'},
'top': {s: '50px', l: '88px', xl: '125px'}, 'top': {s: '50px', l: '88px', xl: '125px'},
'bottom': {s: '55px', l: '70px', xl: '77px'} 'bottom': {s: '55px', l: '98px', xl: '145px'}
}, },
"#tools_bottom": { "#tools_bottom": {
'left': {s: '27px', l: '46px', xl: '65px'}, 'left': {s: '27px', l: '46px', xl: '65px'},
'height': {s: '58px', l: '70px', xl: '77px'} 'height': {s: '58px', l: '98px', xl: '145px'}
}, },
"#color_tools": { "#color_tools": {
'border-spacing': {s: '0 1px'} 'border-spacing': {s: '0 1px'}
@ -2060,7 +2136,8 @@
'height': {s: '20px'} 'height': {s: '20px'}
}, },
"#tool_opacity": { "#tool_opacity": {
'top': {s: '1px'} 'top': {s: '1px'},
'height': {s: 'auto', l:'auto', xl:'auto'}
}, },
"#tools_top input, #tools_bottom input": { "#tools_top input, #tools_bottom input": {
'margin-top': {s: '2px', l: '4px', xl: '5px'}, 'margin-top': {s: '2px', l: '4px', xl: '5px'},
@ -2888,7 +2965,7 @@
keyval += ''; keyval += '';
$.each(keyval.split('/'), function(i, key) { $.each(keyval.split('/'), function(i, key) {
$(document).bind('keydown', {combi: key, disableInInput: disInInp}, function(e) { $(document).bind('keydown', key, function(e) {
fn(); fn();
if(pd) { if(pd) {
e.preventDefault(); e.preventDefault();
@ -2917,7 +2994,7 @@
// Misc additional actions // Misc additional actions
// Make "return" keypress trigger the change event // Make "return" keypress trigger the change event
$('.attr_changer, #image_url').bind('keydown', {combi:'return'}, $('.attr_changer, #image_url').bind('keydown', 'return',
function(evt) {$(this).change();evt.preventDefault();} function(evt) {$(this).change();evt.preventDefault();}
); );
@ -2994,20 +3071,44 @@
$('#group_opacity').SpinButton({ step: 5, min: 0, max: 100, callback: changeOpacity }); $('#group_opacity').SpinButton({ step: 5, min: 0, max: 100, callback: changeOpacity });
$('#zoom').SpinButton({ min: 0.001, max: 10000, step: 50, stepfunc: stepZoom, callback: changeZoom }); $('#zoom').SpinButton({ min: 0.001, max: 10000, step: 50, stepfunc: stepZoom, callback: changeZoom });
window.onbeforeunload = function() {
// Suppress warning if page is empty
if(svgCanvas.getHistoryPosition() === 0) {
show_save_warning = false;
}
// show_save_warning is set to "false" when the page is saved.
if(!curConfig.no_save_warning && show_save_warning) {
// Browser already asks question about closing the page
return "There are unsaved changes.";
}
};
// use HTML5 File API: http://www.w3.org/TR/FileAPI/ // use HTML5 File API: http://www.w3.org/TR/FileAPI/
// if browser has HTML5 File API support, then we will show the open menu item // if browser has HTML5 File API support, then we will show the open menu item
// and provide a file input to click. When that change event fires, it will // and provide a file input to click. When that change event fires, it will
// get the text contents of the file and send it to the canvas // get the text contents of the file and send it to the canvas
if (window.FileReader) { if (window.FileReader) {
var inp = $('<input type="file">').change(function() { var inp = $('<input type="file">').change(function() {
var f = this;
var openFile = function(ok) {
if(!ok) return;
svgCanvas.clear();
if(f.files.length==1) {
var reader = new FileReader();
reader.onloadend = function(e) {
svgCanvas.setSvgString(e.target.result);
updateCanvas();
};
reader.readAsText(f.files[0]);
}
}
$('#main_menu').hide(); $('#main_menu').hide();
if(this.files.length==1) { if(svgCanvas.getHistoryPosition() === 0) {
var reader = new FileReader(); openFile(true);
reader.onloadend = function(e) { } else {
svgCanvas.setSvgString(e.target.result); $.confirm(uiStrings.QwantToOpen, openFile);
updateCanvas();
};
reader.readAsText(this.files[0]);
} }
}); });
$("#tool_open").show().prepend(inp); $("#tool_open").show().prepend(inp);
@ -3092,7 +3193,7 @@
updateCanvas(true); updateCanvas(true);
// }); // });
// var revnums = "svg-editor.js ($Rev: 1470 $) "; // var revnums = "svg-editor.js ($Rev: 1498 $) ";
// revnums += svgCanvas.getVersion(); // revnums += svgCanvas.getVersion();
// $('#copyright')[0].setAttribute("title", revnums); // $('#copyright')[0].setAttribute("title", revnums);

View file

@ -97,8 +97,8 @@ var isOpera = !!window.opera,
"defs": [], "defs": [],
"desc": [], "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"], "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", "id", "requiredFeatures", "stdDeviation"], "feGaussianBlur": ["class", "color-interpolation-filters", "id", "requiredFeatures", "stdDeviation"],
"filter": ["class", "filterRes", "filterUnits", "height", "id", "primitiveUnits", "requiredFeatures", "width", "x", "xlink:href", "y"], "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"], "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"], "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"],
"image": ["class", "clip-path", "clip-rule", "filter", "height", "id", "mask", "opacity", "requiredFeatures", "style", "systemLanguage", "transform", "width", "x", "xlink:href", "xlink:title", "y"], "image": ["class", "clip-path", "clip-rule", "filter", "height", "id", "mask", "opacity", "requiredFeatures", "style", "systemLanguage", "transform", "width", "x", "xlink:href", "xlink:title", "y"],
@ -1143,6 +1143,8 @@ function BatchCommand(text) {
'opacity':1, 'opacity':1,
'stroke':'none', 'stroke':'none',
'stroke-dasharray':'none', 'stroke-dasharray':'none',
'stroke-linejoin':'miter',
'stroke-linecap':'butt',
'stroke-opacity':1, 'stroke-opacity':1,
'stroke-width':1, 'stroke-width':1,
'rx':0, 'rx':0,
@ -1171,6 +1173,20 @@ function BatchCommand(text) {
current_layer.appendChild(shape); current_layer.appendChild(shape);
} }
} }
if(data.curStyles) {
assignAttributes(shape, {
"fill": cur_shape.fill,
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_dasharray,
"stroke-linejoin": cur_shape.stroke_linejoin,
"stroke-linecap": cur_shape.stroke_linecap,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
}, 100);
}
assignAttributes(shape, data.attr, 100); assignAttributes(shape, data.attr, 100);
cleanupElement(shape); cleanupElement(shape);
return shape; return shape;
@ -1211,7 +1227,9 @@ function BatchCommand(text) {
stroke_paint: null, stroke_paint: null,
stroke_opacity: curConfig.initStroke.opacity, stroke_opacity: curConfig.initStroke.opacity,
stroke_width: curConfig.initStroke.width, stroke_width: curConfig.initStroke.width,
stroke_style: 'none', stroke_dasharray: 'none',
stroke_linejoin: 'miter',
stroke_linecap: 'butt',
opacity: curConfig.initOpacity opacity: curConfig.initOpacity
} }
}; };
@ -1320,6 +1338,10 @@ function BatchCommand(text) {
undoStack.push(cmd); undoStack.push(cmd);
undoStackPointer = undoStack.length; undoStackPointer = undoStack.length;
}; };
this.getHistoryPosition = function() {
return undoStackPointer;
};
// private functions // private functions
var getId = function() { var getId = function() {
@ -1953,7 +1975,7 @@ function BatchCommand(text) {
break; break;
case 9: // relative quad (q) case 9: // relative quad (q)
case 8: // absolute quad (Q) case 8: // absolute quad (Q)
dstr += seg.x + "," + seg.y + " " + seg.x1 + "," + seg.y1 + " "; dstr += seg.x1 + "," + seg.y1 + " " + seg.x + "," + seg.y + " ";
break; break;
case 11: // relative elliptical arc (a) case 11: // relative elliptical arc (a)
case 10: // absolute elliptical arc (A) case 10: // absolute elliptical arc (A)
@ -1962,7 +1984,7 @@ function BatchCommand(text) {
break; break;
case 17: // relative smooth cubic (s) case 17: // relative smooth cubic (s)
case 16: // absolute smooth cubic (S) case 16: // absolute smooth cubic (S)
dstr += seg.x + "," + seg.y + " " + seg.x2 + "," + seg.y2 + " "; dstr += seg.x2 + "," + seg.y2 + " " + seg.x + "," + seg.y + " ";
break; break;
} }
} }
@ -2130,12 +2152,14 @@ function BatchCommand(text) {
var angle = canvas.getRotationAngle(child); var angle = canvas.getRotationAngle(child);
var old_start_transform = start_transform; var old_start_transform = start_transform;
var childxforms = [];
start_transform = child.getAttribute("transform"); start_transform = child.getAttribute("transform");
if(angle || hasMatrixTransform(childTlist)) { if(angle || hasMatrixTransform(childTlist)) {
var e2t = svgroot.createSVGTransform(); var e2t = svgroot.createSVGTransform();
e2t.setMatrix(matrixMultiply(tm, sm, tmn, m)); e2t.setMatrix(matrixMultiply(tm, sm, tmn, m));
childTlist.clear(); childTlist.clear();
childTlist.appendItem(e2t,0); childTlist.appendItem(e2t);
childxforms.push(e2t);
} }
// if not rotated or skewed, push the [T][S][-T] down to the child // if not rotated or skewed, push the [T][S][-T] down to the child
else { else {
@ -2144,6 +2168,7 @@ function BatchCommand(text) {
// slide the [T][S][-T] from the front to the back // slide the [T][S][-T] from the front to the back
// [T][S][-T][M] = [M][T2][S2][-T2] // [T][S][-T][M] = [M][T2][S2][-T2]
// (only bringing [-T] to the right of [M])
// [T][S][-T][M] = [T][S][M][-T2] // [T][S][-T][M] = [T][S][M][-T2]
// [-T2] = [M_inv][-T][M] // [-T2] = [M_inv][-T][M]
var t2n = matrixMultiply(m.inverse(), tmn, m); var t2n = matrixMultiply(m.inverse(), tmn, m);
@ -2165,8 +2190,28 @@ function BatchCommand(text) {
childTlist.appendItem(translateBack); childTlist.appendItem(translateBack);
childTlist.appendItem(scale); childTlist.appendItem(scale);
childTlist.appendItem(translateOrigin); childTlist.appendItem(translateOrigin);
childxforms.push(translateBack);
childxforms.push(scale);
childxforms.push(translateOrigin);
logMatrix(translateBack.matrix);
logMatrix(scale.matrix);
} // not rotated } // not rotated
batchCmd.addSubCommand( recalculateDimensions(child) ); batchCmd.addSubCommand( recalculateDimensions(child) );
// TODO: If any <use> have this group as a parent and are
// referencing this child, then we need to impose a reverse
// scale on it so that when it won't get double-translated
// var uses = selected.getElementsByTagNameNS(svgns, "use");
// var href = "#"+child.id;
// var u = uses.length;
// while (u--) {
// var useElem = uses.item(u);
// if(href == useElem.getAttributeNS(xlinkns, "href")) {
// var usexlate = svgroot.createSVGTransform();
// usexlate.setTranslate(-tx,-ty);
// canvas.getTransformList(useElem).insertItemBefore(usexlate,0);
// batchCmd.addSubCommand( recalculateDimensions(useElem) );
// }
// }
start_transform = old_start_transform; start_transform = old_start_transform;
} // element } // element
} // for each child } // for each child
@ -2216,6 +2261,21 @@ function BatchCommand(text) {
newxlate.setTranslate(tx,ty); newxlate.setTranslate(tx,ty);
childTlist.insertItemBefore(newxlate, 0); childTlist.insertItemBefore(newxlate, 0);
batchCmd.addSubCommand( recalculateDimensions(child) ); batchCmd.addSubCommand( recalculateDimensions(child) );
// If any <use> have this group as a parent and are
// referencing this child, then impose a reverse translate on it
// so that when it won't get double-translated
var uses = selected.getElementsByTagNameNS(svgns, "use");
var href = "#"+child.id;
var u = uses.length;
while (u--) {
var useElem = uses.item(u);
if(href == useElem.getAttributeNS(xlinkns, "href")) {
var usexlate = svgroot.createSVGTransform();
usexlate.setTranslate(-tx,-ty);
canvas.getTransformList(useElem).insertItemBefore(usexlate,0);
batchCmd.addSubCommand( recalculateDimensions(useElem) );
}
}
start_transform = old_start_transform; start_transform = old_start_transform;
} }
} }
@ -2810,6 +2870,7 @@ function BatchCommand(text) {
// and do nothing else // and do nothing else
var mouseDown = function(evt) var mouseDown = function(evt)
{ {
if(evt.button === 1 || canvas.spaceKey) return;
root_sctm = svgcontent.getScreenCTM().inverse(); root_sctm = svgcontent.getScreenCTM().inverse();
var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ), var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
mouse_x = pt.x * current_zoom, mouse_x = pt.x * current_zoom,
@ -2964,17 +3025,13 @@ function BatchCommand(text) {
var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width; var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
addSvgElementFromJson({ addSvgElementFromJson({
"element": "polyline", "element": "polyline",
"curStyles": true,
"attr": { "attr": {
"points": d_attr, "points": d_attr,
"id": getNextId(), "id": getNextId(),
"fill": "none", "fill": "none",
"stroke": cur_shape.stroke,
"stroke-width": stroke_w,
"stroke-dasharray": cur_shape.stroke_style,
"stroke-opacity": cur_shape.stroke_opacity,
"stroke-linecap": "round",
"stroke-linejoin": "round",
"opacity": cur_shape.opacity / 2, "opacity": cur_shape.opacity / 2,
"stroke-linecap": "round",
"style": "pointer-events:none" "style": "pointer-events:none"
} }
}); });
@ -3011,20 +3068,14 @@ function BatchCommand(text) {
start_y = y; start_y = y;
addSvgElementFromJson({ addSvgElementFromJson({
"element": "rect", "element": "rect",
"curStyles": true,
"attr": { "attr": {
"x": x, "x": x,
"y": y, "y": y,
"width": 0, "width": 0,
"height": 0, "height": 0,
"id": getNextId(), "id": getNextId(),
"fill": cur_shape.fill, "opacity": cur_shape.opacity / 2
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
} }
}); });
break; break;
@ -3033,6 +3084,7 @@ function BatchCommand(text) {
var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width; var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
addSvgElementFromJson({ addSvgElementFromJson({
"element": "line", "element": "line",
"curStyles": true,
"attr": { "attr": {
"x1": x, "x1": x,
"y1": y, "y1": y,
@ -3041,7 +3093,9 @@ function BatchCommand(text) {
"id": getNextId(), "id": getNextId(),
"stroke": cur_shape.stroke, "stroke": cur_shape.stroke,
"stroke-width": stroke_w, "stroke-width": stroke_w,
"stroke-dasharray": cur_shape.stroke_style, "stroke-dasharray": cur_shape.stroke_dasharray,
"stroke-linejoin": cur_shape.stroke_linejoin,
"stroke-linecap": cur_shape.stroke_linecap,
"stroke-opacity": cur_shape.stroke_opacity, "stroke-opacity": cur_shape.stroke_opacity,
"fill": "none", "fill": "none",
"opacity": cur_shape.opacity / 2, "opacity": cur_shape.opacity / 2,
@ -3053,19 +3107,13 @@ function BatchCommand(text) {
started = true; started = true;
addSvgElementFromJson({ addSvgElementFromJson({
"element": "circle", "element": "circle",
"curStyles": true,
"attr": { "attr": {
"cx": x, "cx": x,
"cy": y, "cy": y,
"r": 0, "r": 0,
"id": getNextId(), "id": getNextId(),
"fill": cur_shape.fill, "opacity": cur_shape.opacity / 2
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
} }
}); });
break; break;
@ -3073,20 +3121,14 @@ function BatchCommand(text) {
started = true; started = true;
addSvgElementFromJson({ addSvgElementFromJson({
"element": "ellipse", "element": "ellipse",
"curStyles": true,
"attr": { "attr": {
"cx": x, "cx": x,
"cy": y, "cy": y,
"rx": 0, "rx": 0,
"ry": 0, "ry": 0,
"id": getNextId(), "id": getNextId(),
"fill": cur_shape.fill, "opacity": cur_shape.opacity / 2
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
} }
}); });
break; break;
@ -3094,22 +3136,16 @@ function BatchCommand(text) {
started = true; started = true;
var newText = addSvgElementFromJson({ var newText = addSvgElementFromJson({
"element": "text", "element": "text",
"curStyles": true,
"attr": { "attr": {
"x": x, "x": x,
"y": y, "y": y,
"id": getNextId(), "id": getNextId(),
"fill": cur_text.fill, "fill": cur_text.fill,
"stroke": cur_shape.stroke,
"stroke-width": cur_text.stroke_width, "stroke-width": cur_text.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
// fix for bug where text elements were always 50% opacity
"opacity": cur_shape.opacity,
"font-size": cur_text.font_size, "font-size": cur_text.font_size,
"font-family": cur_text.font_family, "font-family": cur_text.font_family,
"text-anchor": "middle", "text-anchor": "middle",
"style": "pointer-events:inherit",
"xml:space": "preserve" "xml:space": "preserve"
} }
}); });
@ -3155,6 +3191,7 @@ function BatchCommand(text) {
var mouseMove = function(evt) var mouseMove = function(evt)
{ {
if (!started) return; if (!started) return;
if(evt.button === 1 || canvas.spaceKey) return;
var selected = selectedElements[0], var selected = selectedElements[0],
pt = transformPoint( evt.pageX, evt.pageY, root_sctm ), pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
mouse_x = pt.x * current_zoom, mouse_x = pt.x * current_zoom,
@ -3452,6 +3489,7 @@ function BatchCommand(text) {
var mouseUp = function(evt) var mouseUp = function(evt)
{ {
if(evt.button === 1) return;
var tempJustSelected = justSelected; var tempJustSelected = justSelected;
justSelected = null; justSelected = null;
if (!started) return; if (!started) return;
@ -3486,7 +3524,9 @@ function BatchCommand(text) {
cur_properties.stroke = selected.getAttribute("stroke"); cur_properties.stroke = selected.getAttribute("stroke");
cur_properties.stroke_opacity = selected.getAttribute("stroke-opacity"); cur_properties.stroke_opacity = selected.getAttribute("stroke-opacity");
cur_properties.stroke_width = selected.getAttribute("stroke-width"); cur_properties.stroke_width = selected.getAttribute("stroke-width");
cur_properties.stroke_style = selected.getAttribute("stroke-dasharray"); cur_properties.stroke_dasharray = selected.getAttribute("stroke-dasharray");
cur_properties.stroke_linejoin = selected.getAttribute("stroke-linejoin");
cur_properties.stroke_linecap = selected.getAttribute("stroke-linecap");
} }
if (selected.tagName == "text") { if (selected.tagName == "text") {
cur_text.font_size = selected.getAttribute("font-size"); cur_text.font_size = selected.getAttribute("font-size");
@ -3577,20 +3617,13 @@ function BatchCommand(text) {
(freehand.maxy - freehand.miny) > 0) { (freehand.maxy - freehand.miny) > 0) {
element = addSvgElementFromJson({ element = addSvgElementFromJson({
"element": "ellipse", "element": "ellipse",
"curStyles": true,
"attr": { "attr": {
"cx": (freehand.minx + freehand.maxx) / 2, "cx": (freehand.minx + freehand.maxx) / 2,
"cy": (freehand.miny + freehand.maxy) / 2, "cy": (freehand.miny + freehand.maxy) / 2,
"rx": (freehand.maxx - freehand.minx) / 2, "rx": (freehand.maxx - freehand.minx) / 2,
"ry": (freehand.maxy - freehand.miny) / 2, "ry": (freehand.maxy - freehand.miny) / 2,
"id": getId(), "id": getId()
"fill": cur_shape.fill,
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"opacity": cur_shape.opacity,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"style": "pointer-events:inherit"
} }
}); });
call("changed",[element]); call("changed",[element]);
@ -3602,20 +3635,13 @@ function BatchCommand(text) {
(freehand.maxy - freehand.miny) > 0) { (freehand.maxy - freehand.miny) > 0) {
element = addSvgElementFromJson({ element = addSvgElementFromJson({
"element": "rect", "element": "rect",
"curStyles": true,
"attr": { "attr": {
"x": freehand.minx, "x": freehand.minx,
"y": freehand.miny, "y": freehand.miny,
"width": (freehand.maxx - freehand.minx), "width": (freehand.maxx - freehand.minx),
"height": (freehand.maxy - freehand.miny), "height": (freehand.maxy - freehand.miny),
"id": getId(), "id": getId()
"fill": cur_shape.fill,
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"opacity": cur_shape.opacity,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"style": "pointer-events:inherit"
} }
}); });
call("changed",[element]); call("changed",[element]);
@ -4589,19 +4615,13 @@ function BatchCommand(text) {
// create new path element // create new path element
element = addSvgElementFromJson({ element = addSvgElementFromJson({
"element": "path", "element": "path",
"attr": { "curStyles": true,
"id": getId(), "attr": {
"d": d, "id": getId(),
"fill": "none", "d": d,
"stroke": cur_shape.stroke, "fill": "none"
"stroke-width": cur_shape.stroke_width, }
"stroke-dasharray": cur_shape.stroke_style, });
"opacity": cur_shape.opacity,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"style": "pointer-events:inherit"
}
});
call("changed",[element]); call("changed",[element]);
} }
return element; return element;
@ -4856,17 +4876,12 @@ function BatchCommand(text) {
d_attr = "M" + x + "," + y + " "; d_attr = "M" + x + "," + y + " ";
addSvgElementFromJson({ addSvgElementFromJson({
"element": "path", "element": "path",
"curStyles": true,
"attr": { "attr": {
"d": d_attr, "d": d_attr,
"id": getNextId(), "id": getNextId(),
"fill": cur_shape.fill,
"fill-opacity": cur_shape.fill_opacity,
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style,
"stroke-opacity": cur_shape.stroke_opacity,
"opacity": cur_shape.opacity / 2, "opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
} }
}); });
// set stretchy line to first point // set stretchy line to first point
@ -5582,7 +5597,9 @@ function BatchCommand(text) {
"fill-opacity": cur_shape.fill_opacity, "fill-opacity": cur_shape.fill_opacity,
"stroke": cur_shape.stroke, "stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width, "stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_style, "stroke-dasharray": cur_shape.stroke_dasharray,
"stroke-linejoin": cur_shape.stroke_linejoin,
"stroke-linecap": cur_shape.stroke_linecap,
"stroke-opacity": cur_shape.stroke_opacity, "stroke-opacity": cur_shape.stroke_opacity,
"opacity": cur_shape.opacity, "opacity": cur_shape.opacity,
"visibility":"hidden" "visibility":"hidden"
@ -5819,6 +5836,10 @@ function BatchCommand(text) {
if (extensions["Arrows"]) call("unsetarrownonce") ; if (extensions["Arrows"]) call("unsetarrownonce") ;
} else { } else {
randomize_ids = true; randomize_ids = true;
if (!svgcontent.getAttributeNS(se_ns, 'nonce')) {
svgcontent.setAttributeNS(se_ns, 'se:nonce', nonce);
if (extensions["Arrows"]) call("setarrownonce", nonce) ;
}
} }
} }
@ -5876,6 +5897,53 @@ function BatchCommand(text) {
canvas.embedImage(val); canvas.embedImage(val);
}); });
// convert gradients with userSpaceOnUse to objectBoundingBox
$(svgcontent).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
var elems = $(svgcontent).find('[fill=url(#' + grad.id + ')],[stroke=url(#' + grad.id + ')]');
if(!elems.length) return;
// get object's bounding box
var bb = elems[0].getBBox();
if(grad.tagName === 'linearGradient') {
var g_coords = $(grad).attr(['x1', 'y1', 'x2', 'y2']);
$(grad).attr({
x1: (g_coords.x1 - bb.x) / bb.width,
y1: (g_coords.y1 - bb.y) / bb.height,
x2: (g_coords.x2 - bb.x) / bb.width,
y2: (g_coords.y1 - bb.y) / bb.height
});
grad.removeAttribute('gradientUnits');
} else {
// Note: radialGradient elements cannot be easily converted
// because userSpaceOnUse will keep circular gradients, while
// objectBoundingBox will x/y scale the gradient according to
// its bbox.
// For now we'll do nothing, though we should probably have
// the gradient be updated as the element is moved, as
// inkscape/illustrator do.
// var g_coords = $(grad).attr(['cx', 'cy', 'r']);
//
// $(grad).attr({
// cx: (g_coords.cx - bb.x) / bb.width,
// cy: (g_coords.cy - bb.y) / bb.height,
// r: g_coords.r
// });
//
// grad.removeAttribute('gradientUnits');
}
}
});
// Fix XML for Opera/Win/Non-EN // Fix XML for Opera/Win/Non-EN
if(!support.goodDecimals) { if(!support.goodDecimals) {
canvas.fixOperaXML(svgcontent, newDoc.documentElement); canvas.fixOperaXML(svgcontent, newDoc.documentElement);
@ -6985,12 +7053,8 @@ function BatchCommand(text) {
} }
}; };
this.getStrokeStyle = function() { this.setStrokeAttr = function(attr, val) {
return cur_shape.stroke_style; cur_shape[attr.replace('-','_')] = val;
};
this.setStrokeStyle = function(val) {
cur_shape.stroke_style = val;
var elems = []; var elems = [];
var i = selectedElements.length; var i = selectedElements.length;
while (i--) { while (i--) {
@ -7003,10 +7067,10 @@ function BatchCommand(text) {
} }
} }
if (elems.length > 0) { if (elems.length > 0) {
this.changeSelectedAttribute("stroke-dasharray", val, elems); this.changeSelectedAttribute(attr, val, elems);
} }
}; };
this.getOpacity = function() { this.getOpacity = function() {
return cur_shape.opacity; return cur_shape.opacity;
}; };
@ -8239,7 +8303,7 @@ function BatchCommand(text) {
// Function: getVersion // Function: getVersion
// Returns a string which describes the revision number of SvgCanvas. // Returns a string which describes the revision number of SvgCanvas.
this.getVersion = function() { this.getVersion = function() {
return "svgcanvas.js ($Rev: 1470 $)"; return "svgcanvas.js ($Rev: 1498 $)";
}; };
this.setUiStrings = function(strs) { this.setUiStrings = function(strs) {

View file

@ -293,6 +293,17 @@ $(function() {
var svgroot = svgdoc.createElementNS(svgns, "svg"); var svgroot = svgdoc.createElementNS(svgns, "svg");
svgroot.setAttributeNS(svgns, 'viewBox', [0,0,icon_w,icon_h].join(' ')); svgroot.setAttributeNS(svgns, 'viewBox', [0,0,icon_w,icon_h].join(' '));
// Make flexible by converting width/height to viewBox
var w = svg.getAttribute('width');
var h = svg.getAttribute('height');
svg.removeAttribute('width');
svg.removeAttribute('height');
var vb = svg.getAttribute('viewBox');
if(!vb) {
svg.setAttribute('viewBox', [0,0,w,h].join(' '));
}
$(svgroot).attr({ $(svgroot).attr({
"xmlns": svgns, "xmlns": svgns,
"width": icon_w, "width": icon_w,