├── .gitignore ├── Gruntfile.js ├── JSYG.FullEditor.css ├── JSYG.FullEditor.js ├── LICENSE ├── README.md ├── examples ├── firefox.svg └── linux.svg ├── index.html ├── package-lock.json ├── package.json ├── script.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | 5 | 6 | 7 | var cssFiles = [ 8 | "jsyg-boundingbox/JSYG.BoundingBox.css", 9 | "jsyg-editor/JSYG.Editor.css", 10 | "jsyg-fulleditor/JSYG.FullEditor.css", 11 | "jsyg-selection/JSYG.Selection.css", 12 | "jsyg-texteditor/JSYG.TextEditor.css", 13 | "jsyg-zoomAndPan/JSYG.ZoomAndPan.css", 14 | ] 15 | 16 | cssFiles = cssFiles.map(function(file) { return "bower_components/"+file; }) 17 | 18 | cssFiles.push("styles.css") 19 | 20 | 21 | grunt.initConfig({ 22 | concat:{ 23 | options: { 24 | separator: '\n\n', 25 | }, 26 | dist: { 27 | src: cssFiles, 28 | dest: 'JSYG.FullEditor.css' 29 | } 30 | } 31 | }); 32 | 33 | grunt.loadNpmTasks('grunt-contrib-concat'); 34 | 35 | grunt.registerTask('default', ['concat']); 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /JSYG.FullEditor.css: -------------------------------------------------------------------------------- 1 | svg g.fillBox path { 2 | fill:black; 3 | fill-opacity:0.1; 4 | stroke:black; 5 | stroke-width:0.5; 6 | stroke-dasharray:4,4; 7 | } 8 | svg g.fillBox path.shadow,svg g.strokeBox path.shadow { 9 | fill:none; 10 | stroke:white; 11 | stroke-width:1; 12 | } 13 | 14 | div.fillBox { 15 | opacity:0.5; 16 | filter:alpha(opacity = 50); 17 | border:1px dashed gray; 18 | position:absolute; 19 | background-color:#CCC; 20 | } 21 | 22 | svg g.strokeBox path { 23 | fill:none; 24 | stroke:mediumvioletred; 25 | stroke-width:1; 26 | stroke-dasharray:4,4; 27 | } 28 | div.strokeBox { 29 | border:2px dashed mediumvioletred; 30 | background-color:transparent; 31 | position:absolute; 32 | } 33 | 34 | svg g.fillBox circle{ 35 | fill:white; 36 | stroke:black; 37 | stroke-width:0.2; 38 | } 39 | svg g.fillBox .mainPoints circle{ 40 | fill:brown; 41 | stroke:none; 42 | cursor:default; 43 | } 44 | svg g.fillBox .mainPoints circle.closingPoint{ 45 | fill:green; 46 | } 47 | svg g.fillBox .ctrlPoints circle{ 48 | fill:gold; 49 | stroke:none; 50 | cursor:default; 51 | } 52 | div.resize div { 53 | border-radius:5px; 54 | background-color:white; 55 | border:1px solid black; 56 | position:absolute; 57 | } 58 | div.rotate div { 59 | border-radius:5px; 60 | background-color:white; 61 | border:1px solid black; 62 | position:absolute; 63 | } 64 | 65 | div#Selection { 66 | position:absolute; 67 | opacity:0.2; 68 | filter:alpha(opacity=20); 69 | border:1px dashed black; 70 | position:absolute; 71 | background-color:#CCC; 72 | z-index:100; 73 | } 74 | 75 | svg g.textBox path { 76 | fill:#bbb; 77 | fill-opacity:0.1; 78 | stroke:black; 79 | stroke-width:0.5; 80 | stroke-dasharray:4,4; 81 | } 82 | svg g.textBox rect.textSelection { 83 | fill:orange; 84 | fill-opacity:0.5; 85 | } 86 | svg g.textBox line.cursor { 87 | fill:none; 88 | stroke:black; 89 | stroke-width:1; 90 | } 91 | 92 | rect.marqueeZoom { 93 | fill:black; 94 | fill-opacity:0.1; 95 | stroke:black; 96 | stroke-width:0.5; 97 | stroke-dasharray:4,4; 98 | } 99 | div.marqueeZoom { 100 | opacity:0.2; 101 | filter:alpha(opacity = 0.2); 102 | border:1px dashed black; 103 | position:absolute; 104 | background-color:#CCC; 105 | z-index:100; 106 | } 107 | .SVGResize { 108 | position:relative; 109 | left:-18px; 110 | cursor: nw-resize; 111 | width:18px; 112 | height:18px; 113 | padding:0; 114 | display:inline-block; 115 | background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAABvklEQVQ4jaWTvW5TQRBGR0KIlyAVFU9gJCQaJDroDE4k0iA5FLaQEt0b9nf2emdmNy1p3KRJRREKKiok3iuQpVjf+BJEhON5gKNvvjkDsOUY1b5t2/bhVhDn9CSgL1ar5Z0hwen94N2V9+5iPp8/uCNEHQbvfyLar1skUYeIeIlovgMAJI7GGcUbQvR+TbKCEO3mJMVa+2ITyCR4d9Wvw7zYy4lL1/k3G0DMe0T/C9F+qZDY5CSbQlS76uQbAIAI+ZMsRWv9fAUVdOb0P5LgZfD2R4XEg5ykNE3zFABgEdwsJykxxse3QPSkrlOTpETjnLgEa18BAHTevstJSorx30X3svWd1CRcuq7bBQCgRfiYk5RM9PK2Tv6QTYR8TnKdJMZOcuIishgBACCas+Dt+V+QoWzMsTnJ605qEi4xxkcAAFarpTAVovDkxjpr2aon6+v0nfRJtD7+xEyFefH6GuJuyFaNXcvWX6fvxGq1ZKYiEg+IaMcpNQKtj/cC+uK9uwCovzOUjTnK8DqI5kxWSYhoJycuIbgZHB19eGat+TydTu8DADijePg76Mzp0JPg7XnfiVNqFIKbjcfje78BPPMvDAx+oqIAAAAASUVORK5CYII=); 116 | } 117 | .MousePanClosedHand { 118 | cursor:url(data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAATklEQVRYhe3QMQoAIAwEwfz/07ExhU2ixWnAHRAshFs0AxryebK7bjxkd2WIV+LN0whlwPLdtwO2hlsEKMaPIpQBZYR6vIz4JgAAAPxjAJ+/ZalaZD7tAAAAAElFTkSuQmCC) 4 4, auto 119 | } 120 | .MousePanOpenHand { 121 | cursor:url(data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAaklEQVRYhe3SQQrAIAxEUe9/6bhRCJoYBUcF50MXbRfzik1pnJTrWHpQauo5FNMN6vvm3RMACMQdtXAIxBQAcRzmz2eFALhnH7Ud0H7hiXETcQOwjLgKQIwvIZCAEIEeDxHfABhjjDH2TxnUVyH711vfkQAAAABJRU5ErkJggg==) 4 4, auto 122 | } 123 | 124 | .jsyg-doc-frame { 125 | fill: #FFF; 126 | stroke: #000; 127 | stroke-width: 0.2; 128 | } 129 | .jsyg-doc-shadow { 130 | fill: #808080; 131 | stroke: none; 132 | } 133 | -------------------------------------------------------------------------------- /JSYG.FullEditor.js: -------------------------------------------------------------------------------- 1 | /*jshint forin:false, eqnull:true*/ 2 | /* globals JSYG*/ 3 | 4 | (function(factory) { 5 | 6 | if (typeof module == "object" && typeof module.exports == "object") { 7 | 8 | module.exports = factory( 9 | require("jsyg"), 10 | require("jsyg-editor"), 11 | require("jsyg-texteditor"), 12 | require("jsyg-zoomandpan"), 13 | require("jsyg-pathdrawer"), 14 | require("jsyg-polylinedrawer"), 15 | require("jsyg-shapedrawer"), 16 | require("jsyg-undoredo"), 17 | require("jquery.hotkeys") 18 | ); 19 | } 20 | else if (typeof define != "undefined" && define.amd) { 21 | 22 | define("jsyg-fulleditor",[ 23 | "jsyg", 24 | "jsyg-editor", 25 | "jsyg-texteditor", 26 | "jsyg-zoomandpan", 27 | "jsyg-pathdrawer", 28 | "jsyg-polylinedrawer", 29 | "jsyg-shapedrawer", 30 | "jsyg-undoredo", 31 | "jquery.hotkeys" 32 | ],factory); 33 | } 34 | else if (typeof JSYG != "undefined") { 35 | 36 | var deps = ["Editor","TextEditor","ZoomAndPan","PathDrawer","PolylineDrawer","ShapeDrawer","UndoRedo"]; 37 | 38 | deps = deps.map(function(dep) { 39 | if (!JSYG[dep]) throw new Error("JSYG."+dep+" is missing"); 40 | return JSYG[dep]; 41 | }); 42 | 43 | deps.unshift(JSYG); 44 | 45 | factory.apply(null,deps); 46 | } 47 | else throw new Error("JSYG is needed"); 48 | 49 | })(function(JSYG,Editor,TextEditor,ZoomAndPan,PathDrawer,PolylineDrawer,ShapeDrawer,UndoRedo) { 50 | 51 | "use strict"; 52 | 53 | var slice = Array.prototype.slice; 54 | 55 | function FullEditor(node,opt) { 56 | 57 | this._bindFunctions(); 58 | 59 | this._init(); 60 | 61 | this._keyShortCuts = {}; 62 | 63 | if (node) this.setNode(node); 64 | 65 | if (opt) this.enable(opt); 66 | } 67 | 68 | FullEditor._plugins = []; 69 | 70 | FullEditor.prototype = Object.create(JSYG.StdConstruct.prototype); 71 | 72 | FullEditor.prototype.constructor = FullEditor; 73 | 74 | //events 75 | [ 76 | 'onload', 77 | 'ondrag', 78 | 'ondraw', 79 | 'oninsert', 80 | 'onremove', 81 | 'onchange', 82 | 'onzoom', 83 | 'onchangetarget', 84 | 'ondisableedition' 85 | 86 | ].forEach(function(event) { FullEditor.prototype[event] = null; }); 87 | 88 | FullEditor.prototype.idContainer = "containerDoc"; 89 | 90 | FullEditor.prototype._initPlugins = function() { 91 | 92 | FullEditor._plugins.forEach(this._createPlugin.bind(this)); 93 | 94 | this._applyMethodPlugins("init"); 95 | 96 | return this; 97 | }; 98 | 99 | FullEditor.prototype._init = function() { 100 | 101 | this._initUndoRedo(); 102 | 103 | this._initShapeEditor(); 104 | 105 | this._initZoomAndPan(); 106 | 107 | this._initTextEditor(); 108 | 109 | this._initShapeDrawer(); 110 | 111 | this._initPlugins(); 112 | 113 | return this; 114 | }; 115 | 116 | 117 | FullEditor.prototype._bindFunctions = function() { 118 | 119 | for (var n in this) { 120 | 121 | if (typeof(this[n]) == "function" && n.charAt(0) != '_') this[n] = this[n].bind(this); 122 | } 123 | 124 | return this; 125 | }; 126 | 127 | /** 128 | * Register a key shortcut 129 | * @param {string} key jquery hotkeys syntax (example : "ctrl+i") 130 | * @param {function} fct callback called when key or combination keys are pressed 131 | * @returns {JSYG.FullEditor} 132 | * @description You can also pass an object with several key shortcuts as keys/values 133 | */ 134 | FullEditor.prototype.registerKeyShortCut = function(key,fct) { 135 | 136 | if (JSYG.isPlainObject(key)) { 137 | for (var n in key) this.registerKeyShortCut(n,key[n]); 138 | return this; 139 | } 140 | 141 | if (this._keyShortCuts[key]) this._disableKeyShortCut(key); 142 | 143 | this._keyShortCuts[key] = fct; 144 | 145 | if (this.enabled) this._enableKeyShortCut(key,fct); 146 | 147 | return this; 148 | }; 149 | 150 | /** 151 | * Unregister a key shortcut 152 | * @param {string} key jquery hotkeys syntax (example : "ctrl+i") 153 | * @returns {JSYG.FullEditor} 154 | */ 155 | FullEditor.prototype.unregisterKeyShortCut = function(key) { 156 | 157 | var that = this; 158 | 159 | if (Array.isArray(key) || arguments.length > 1 && (key = [].slice.call(arguments))) { 160 | key.forEach(that.unregisterKeyShortCut); 161 | return this; 162 | } 163 | 164 | this._disableKeyShortCut(key,this._keyShortCuts[key]); 165 | 166 | delete this._keyShortCuts[key]; 167 | 168 | return this; 169 | }; 170 | 171 | /** 172 | * Select all editable elements in document 173 | * @returns {JSYG.FullEditor} 174 | */ 175 | FullEditor.prototype.selectAll = function() { 176 | 177 | this.disableEdition(); 178 | this.enableSelection(); 179 | this.shapeEditor.selection.selectAll(); 180 | 181 | return this; 182 | }; 183 | 184 | /** 185 | * Deselect all editable elements 186 | * @returns {JSYG.FullEditor} 187 | */ 188 | FullEditor.prototype.deselectAll = function() { 189 | 190 | var isEnabled = this.shapeEditor.enabled; 191 | 192 | if (!isEnabled) this.shapeEditor.enable(); 193 | 194 | this.shapeEditor.selection.deselectAll(); 195 | 196 | if (!isEnabled) this.shapeEditor.disable(); 197 | 198 | return this; 199 | }; 200 | 201 | FullEditor.prototype._enableKeyShortCut = function(key,fct) { 202 | 203 | if (typeof fct != 'function') throw new Error(typeof fct + " instead of function expected"); 204 | 205 | new JSYG(document).on('keydown',null,key,fct); 206 | 207 | return this; 208 | }; 209 | 210 | FullEditor.prototype._disableKeyShortCut = function(key,fct) { 211 | 212 | if (typeof fct != 'function') throw new Error(typeof fct + " instead of function expected"); 213 | 214 | new JSYG(document).off('keydown',fct); 215 | 216 | return this; 217 | }; 218 | 219 | /** 220 | * Enable all key shorcuts registered by registerKeyShortCut method 221 | * @returns {JSYG.FullEditor} 222 | * @see JSYG.prototype.registerKeyShortCut 223 | */ 224 | FullEditor.prototype.enableKeyShortCuts = function() { 225 | 226 | var keys = this._keyShortCuts; 227 | 228 | for (var n in keys) this._enableKeyShortCut(n,keys[n]); 229 | 230 | return this; 231 | }; 232 | 233 | /** 234 | * Disable all key shorcuts registered by registerKeyShortCut method 235 | * @returns {JSYG.FullEditor} 236 | * @see JSYG.prototype.registerKeyShortCut 237 | */ 238 | FullEditor.prototype.disableKeyShortCuts = function() { 239 | 240 | var keys = this._keyShortCuts; 241 | 242 | for (var n in keys) this._disableKeyShortCut(n,keys[n]); 243 | 244 | return this; 245 | }; 246 | 247 | FullEditor.prototype._editText = true; 248 | 249 | /** 250 | * @property {boolean} editText set if text elements can be edited or not 251 | */ 252 | Object.defineProperty(FullEditor.prototype,'editText',{ 253 | get : function() { 254 | return this._editText; 255 | }, 256 | set:function(value) { 257 | this._editText = !!value; 258 | if (!value) this.textEditor.hide(); 259 | } 260 | }); 261 | 262 | /** 263 | * @property {boolean} editPosition set if elements position can be edited or not 264 | */ 265 | Object.defineProperty(FullEditor.prototype,'editPosition',{ 266 | get : function() { 267 | return this.shapeEditor.ctrlsDrag.enabled; 268 | }, 269 | set:function(value) { 270 | this.shapeEditor.ctrlsDrag[ (value ? 'en' : 'dis') + 'able'](); 271 | } 272 | }); 273 | 274 | /** 275 | * @property {boolean} editSize set if elements size can be edited or not 276 | */ 277 | Object.defineProperty(FullEditor.prototype,'editSize',{ 278 | get : function() { 279 | return this.shapeEditor.ctrlsResize.enabled; 280 | }, 281 | set:function(value) { 282 | this.shapeEditor.ctrlsResize[ (value ? 'en' : 'dis') + 'able'](); 283 | } 284 | }); 285 | 286 | /** 287 | * @property {boolean} editRotation set if elements rotation can be edited or not 288 | */ 289 | Object.defineProperty(FullEditor.prototype,'editRotation',{ 290 | get : function() { 291 | return this.shapeEditor.ctrlsRotate.enabled; 292 | }, 293 | set:function(value) { 294 | this.shapeEditor.ctrlsRotate[ (value ? 'en' : 'dis') + 'able'](); 295 | } 296 | }); 297 | 298 | /** 299 | * @property {boolean} editPathMainPoints set if main points of paths can be edited or not 300 | */ 301 | Object.defineProperty(FullEditor.prototype,'editPathMainPoints',{ 302 | get : function() { 303 | return this.shapeEditor.ctrlsMainPoints.enabled; 304 | }, 305 | set:function(value) { 306 | this.shapeEditor.ctrlsMainPoints[ (value ? 'en' : 'dis') + 'able'](); 307 | } 308 | }); 309 | 310 | /** 311 | * @property {boolean} editPathCtrlPoints set if control points of paths can be edited or not 312 | */ 313 | Object.defineProperty(FullEditor.prototype,'editPathCtrlPoints',{ 314 | get : function() { 315 | return this.shapeEditor.ctrlsCtrlPoints.enabled; 316 | }, 317 | set:function(value) { 318 | this.shapeEditor.ctrlsCtrlPoints[ (value ? 'en' : 'dis') + 'able'](); 319 | } 320 | }); 321 | 322 | /** 323 | * @property {boolean} canvasResizable set if the editor can be resized or not 324 | */ 325 | Object.defineProperty(FullEditor.prototype,'canvasResizable',{ 326 | get:function() { 327 | return this.zoomAndPan.resizable.enabled; 328 | }, 329 | set:function(value) { 330 | this.zoomAndPan.resizable[ (value ? 'en' : 'dis') + 'able'](); 331 | } 332 | }); 333 | 334 | /** 335 | * @property {boolean} keepShapesRatio set if ratio must be kept when resizing 336 | */ 337 | Object.defineProperty(FullEditor.prototype,'keepShapesRatio',{ 338 | get:function() { 339 | return this.shapeEditor.ctrlsResize.keepRatio; 340 | }, 341 | set:function(value) { 342 | value = !!value; 343 | this.shapeEditor.ctrlsResize.keepRatio = value; 344 | this._keepShapesRatio = value; 345 | if (this.shapeEditor.display) this.shapeEditor.update(); 346 | } 347 | }); 348 | 349 | /** 350 | * @property {string} drawingPathMethod "freehand" or "point2point". Set method of drawing paths 351 | */ 352 | Object.defineProperty(FullEditor.prototype,'drawingPathMethod',{ 353 | get:function() { 354 | return this.pathDrawer.type; 355 | }, 356 | set:function(value) { 357 | 358 | if (value != 'freehand' && value != 'point2point') 359 | throw new Error("Only 'freehand' and 'point2point' are allowed"); 360 | 361 | this.pathDrawer.type = value; 362 | } 363 | }); 364 | 365 | /** 366 | * @property {boolean} autoSmoothPaths set if paths must be smoothed automatically when drawing 367 | */ 368 | Object.defineProperty(FullEditor.prototype,'autoSmoothPaths',{ 369 | get:function() { 370 | return this.shapeEditor.ctrlsMainPoints.autoSmooth; 371 | }, 372 | set:function(value) { 373 | 374 | this.shapeEditor.ctrlsMainPoints.autoSmooth = value; 375 | } 376 | }); 377 | 378 | /** 379 | * @property {boolean} useTransformAttr set if transform attribute must be affected when editing size and position, instead 380 | * of position and size attributes 381 | */ 382 | Object.defineProperty(FullEditor.prototype,'useTransformAttr',{ 383 | 384 | get:function() { 385 | 386 | var dragType = this.shapeEditor.ctrlsDrag.type; 387 | var resizeType = this.shapeEditor.ctrlsResize.type; 388 | 389 | if (dragType!=resizeType) throw new Error("dragType and resizeType are not the same"); 390 | 391 | return dragType; 392 | }, 393 | 394 | set:function(value) { 395 | 396 | var oldValue = this.useTransformAttr; 397 | 398 | if (value != oldValue) { 399 | 400 | this.shapeEditor.ctrlsDrag.type = value ? 'transform' : 'attributes'; 401 | if (this.shapeEditor.ctrlsDrag.enabled) this.shapeEditor.ctrlsDrag.disable().enable(); 402 | 403 | this.shapeEditor.ctrlsResize.type = value ? 'transform' : 'attributes'; 404 | if (this.shapeEditor.ctrlsResize.enabled) this.shapeEditor.ctrlsResize.disable().enable(); 405 | } 406 | } 407 | }); 408 | 409 | FullEditor.prototype._nbLayers = 0; 410 | 411 | FullEditor.prototype._currentLayer = null; 412 | 413 | /** 414 | * @property {number} currentLayer set current layer of edition 415 | */ 416 | Object.defineProperty(FullEditor.prototype,'currentLayer',{ 417 | 418 | get : function() { 419 | return this._currentLayer; 420 | }, 421 | 422 | set : function(value) { 423 | 424 | var $node; 425 | 426 | if (value != null) { 427 | 428 | $node = new JSYG( this._getDocumentSelector() + ' #layer' + value ); 429 | 430 | if (!$node.length) throw new Error("Invalid value for currentLayer. No node found."); 431 | } 432 | 433 | this._currentLayer = value; 434 | 435 | this.hideEditors(); 436 | 437 | this.editableShapes = this.editableShapes; //on force la valeur pour l'actualiser 438 | } 439 | }); 440 | 441 | /** 442 | * Get all layers defined 443 | * @returns {JSYG} 444 | */ 445 | FullEditor.prototype.getLayers = function() { 446 | 447 | return new JSYG(this._getDocumentSelector()).find(".layer"); 448 | }; 449 | 450 | /** 451 | * Add and use a new layer 452 | * @returns {JSYG.FullEditor} 453 | */ 454 | FullEditor.prototype.addLayer = function() { 455 | 456 | var nb = ++this._nbLayers, 457 | id = "layer"+nb, 458 | g = new JSYG('').addClass("layer").attr("id",id).appendTo( this._getDocumentSelector() ); 459 | 460 | this.currentLayer = nb; 461 | 462 | this.triggerChange(); 463 | 464 | return this; 465 | }; 466 | 467 | /** 468 | * Remove a layer 469 | * @returns {JSYG.FullEditor} 470 | */ 471 | FullEditor.prototype.removeLayer = function() { 472 | 473 | if (!this.currentLayer) throw new Error("No layer selected"); 474 | 475 | new JSYG(this._getLayerSelector()).remove(); 476 | 477 | this._actuLayers(); 478 | 479 | this.currentLayer = null; 480 | 481 | this.triggerChange(); 482 | 483 | return this; 484 | }; 485 | 486 | FullEditor.prototype._getLayerSelector = function() { 487 | 488 | return this._getDocumentSelector() + (this.currentLayer ? ' #layer'+this.currentLayer : '')+' '; 489 | }; 490 | 491 | /** 492 | * Get document as a DOM node 493 | * @returns {Element} 494 | */ 495 | FullEditor.prototype.getDocument = function() { 496 | 497 | return document.querySelector( this._getDocumentSelector() ); 498 | }; 499 | 500 | FullEditor.prototype._initUndoRedo = function() { 501 | 502 | var that = this; 503 | 504 | this.undoRedo = new UndoRedo(); 505 | 506 | this.undoRedo.on("change",function() { 507 | //that.hideEditors(); 508 | that.trigger("change", that, that.getDocument() ); 509 | }); 510 | }; 511 | 512 | /** 513 | * Hide shape and text editors 514 | * @returns {JSYG.FullEditor} 515 | */ 516 | FullEditor.prototype.hideEditors = function() { 517 | 518 | this.shapeEditor.hide(); 519 | this.textEditor.hide(); 520 | 521 | return this; 522 | }; 523 | 524 | /** 525 | * Enable mouse pointer selection 526 | * @returns {JSYG.FullEditor} 527 | */ 528 | FullEditor.prototype.enableSelection = function() { 529 | 530 | var target = this.shapeEditor.display && this.shapeEditor.target(); 531 | 532 | this.disableEdition(); 533 | this.shapeEditor.enable(); 534 | 535 | if (target) this.shapeEditor.target(target).show(); 536 | 537 | return this; 538 | }; 539 | 540 | /** 541 | * Disable mouse pointer selection 542 | * @returns {JSYG.FullEditor} 543 | */ 544 | FullEditor.prototype.disableSelection = function() { 545 | 546 | this.hideEditors(); 547 | 548 | if (this.shapeEditor.enabled) this.shapeEditor.disable(); 549 | 550 | return this; 551 | }; 552 | 553 | /** 554 | * Disable mouse pointer insertion 555 | * @returns {JSYG.FullEditor} 556 | */ 557 | FullEditor.prototype.disableInsertion = function() { 558 | 559 | this.disableInsertElement(); 560 | 561 | this.disableShapeDrawer(); 562 | 563 | return this; 564 | }; 565 | 566 | /** 567 | * Register a plugin 568 | * @param {object} plugin 569 | * @returns {JSYG.FullEditor} 570 | */ 571 | FullEditor.registerPlugin = function(plugin) { 572 | 573 | if (!plugin.name) throw new Error("Plugin must have a name property"); 574 | 575 | if (this._plugins.some(function(otherPlugin) { return otherPlugin.name == plugin.name })) 576 | throw new Error(plugin.name+" plugin already exists"); 577 | 578 | this._plugins.push(plugin); 579 | 580 | return this; 581 | }; 582 | 583 | function isPrivate(name) { 584 | 585 | return name.charAt(0) == '_'; 586 | } 587 | 588 | FullEditor.prototype._createPlugin = function(plugin) { 589 | 590 | plugin = Object.create(plugin); 591 | 592 | plugin.set = JSYG.StdConstruct.prototype.set; 593 | 594 | plugin.editor = this; 595 | 596 | this[plugin.name] = function(method) { 597 | 598 | var args = slice.call(arguments,1); 599 | var returnValue; 600 | var prop; 601 | 602 | if (!method || JSYG.isPlainObject(method)) { 603 | args = [method]; 604 | method = "enable"; 605 | } 606 | 607 | if (method == "get") { 608 | 609 | prop = args[0]; 610 | 611 | if (isPrivate(prop)) throw new Error("property "+prop+" is private"); 612 | 613 | return plugin[args[0]]; 614 | } 615 | 616 | if (!plugin[method]) throw new Error("method "+method+" does not exist"); 617 | 618 | if (isPrivate(method)) throw new Error("method "+method+" is private"); 619 | 620 | returnValue = plugin[method].apply(plugin,args); 621 | 622 | return returnValue || this; 623 | }; 624 | }; 625 | 626 | FullEditor.prototype._applyMethodPlugins = function(method) { 627 | 628 | var that = this; 629 | 630 | FullEditor._plugins.forEach(function(plugin) { 631 | 632 | try { that[plugin.name](method); } 633 | catch(e) {} 634 | }); 635 | }; 636 | 637 | /** 638 | * Enable edition functionalities 639 | * @returns {JSYG.FullEditor} 640 | */ 641 | FullEditor.prototype.disableEdition = function() { 642 | 643 | this.disableInsertion(); 644 | 645 | this.disableMousePan(); 646 | 647 | this.disableSelection(); 648 | 649 | this.trigger("disableedition",this); 650 | 651 | return this; 652 | }; 653 | 654 | ["copy","cut","paste"].forEach(function(action) { 655 | 656 | FullEditor.prototype[action] = function() { 657 | 658 | if (action!=="paste" && !this.shapeEditor.display) return; 659 | 660 | this.shapeEditor.clipBoard[action](); 661 | 662 | return this; 663 | }; 664 | }); 665 | 666 | /** 667 | * Duplicate selected element 668 | * @returns {JSYG.FullEditor} 669 | */ 670 | FullEditor.prototype.duplicate = function() { 671 | 672 | var cb = this.shapeEditor.clipBoard, 673 | buffer = cb.buffer; 674 | 675 | cb.copy(); 676 | cb.paste(); 677 | cb.buffer = buffer; 678 | 679 | return this; 680 | }; 681 | 682 | 683 | ["undo","redo"].forEach(function(action) { 684 | 685 | FullEditor.prototype[action] = function() { 686 | 687 | this.hideEditors(); 688 | 689 | this.undoRedo[action](); 690 | 691 | return this; 692 | }; 693 | 694 | }); 695 | 696 | ["Front","Backwards","Forwards","Back"].forEach(function(type) { 697 | 698 | var methode = "move"+type; 699 | 700 | FullEditor.prototype[methode] = function() { 701 | 702 | var target = this.shapeEditor._target; 703 | 704 | if (target) { 705 | new JSYG(target)[methode](); 706 | this.triggerChange(); 707 | } 708 | 709 | return this; 710 | }; 711 | }); 712 | 713 | ["Horiz","Verti"].forEach(function(type) { 714 | 715 | var methode = "move"+type; 716 | 717 | FullEditor.prototype[methode] = function(value) { 718 | 719 | var target = this.shapeEditor.target(); 720 | var dim; 721 | 722 | if (target && target.length) { 723 | 724 | dim = target.getDim(); 725 | 726 | target.setDim( type == "Horiz" ? {x:dim.x+value} : {y:dim.y+value} ); 727 | this.shapeEditor.update(); 728 | 729 | this.triggerChange(); 730 | } 731 | 732 | return this; 733 | }; 734 | }); 735 | 736 | var regOperator = /^\s*(\+|-|\*|\/)=(\d+)\s*$/; 737 | 738 | function parseValue(newValue,oldValue) { 739 | 740 | var matches = regOperator.exec(newValue); 741 | 742 | if (!matches) return newValue; 743 | 744 | switch (matches[1]) { 745 | case '+' : return oldValue + Number(matches[2]); 746 | case '-' : return oldValue - Number(matches[2]); 747 | case '*' : return oldValue * Number(matches[2]); 748 | case '/' : return oldValue / Number(matches[2]); 749 | } 750 | } 751 | 752 | /** 753 | * Get or set dimensions of element selected 754 | * @param {string} prop x, y , width or height 755 | * @param {number} value 756 | * @returns {number,JSYG.FullEditor} 757 | * @description You can also pass an object 758 | */ 759 | FullEditor.prototype.dim = function(prop,value) { 760 | 761 | if (JSYG.isPlainObject(prop) || value != null) return this._setDim(prop,value); 762 | else return this._getDim(prop,value); 763 | }; 764 | 765 | FullEditor.prototype._getDim = function(prop) { 766 | 767 | var target = this.shapeEditor.target(); 768 | var doc = this.getDocument(); 769 | var dim; 770 | 771 | if (!target || !target.length) return null; 772 | 773 | dim = target.getDim(doc); 774 | 775 | return (prop == null) ? dim : dim[prop]; 776 | }; 777 | 778 | FullEditor.prototype._setDim = function(prop,value) { 779 | 780 | var target = this.shapeEditor.target(); 781 | var change = false; 782 | var doc = this.getDocument(); 783 | var n,newDim,oldDim; 784 | 785 | if (!target || !target.length) return this; 786 | 787 | if (JSYG.isPlainObject(prop)) newDim = JSYG.extend({},prop); 788 | else { 789 | newDim = {}; 790 | newDim[prop] = value; 791 | } 792 | 793 | oldDim = target.getDim(doc); 794 | 795 | for (n in newDim) { 796 | 797 | newDim[n] = parseValue(newDim[n],oldDim[n]); 798 | 799 | if (newDim[n] != oldDim[n]) change = true; 800 | } 801 | 802 | if (change) { 803 | 804 | newDim.from = doc; 805 | newDim.keepRatio = this.keepShapesRatio; 806 | 807 | target.setDim(newDim); 808 | this.shapeEditor.update(); 809 | this.triggerChange(); 810 | } 811 | 812 | return this; 813 | }; 814 | 815 | /** 816 | * Rotate selected element 817 | * @param {number} value angle in degrees 818 | * @returns {JSYG.FullEditor} 819 | */ 820 | FullEditor.prototype.rotate = function(value) { 821 | 822 | var target = this.target(), 823 | oldValue = target && target.rotate(); 824 | 825 | if (!target) return (value == null) ? null : this; 826 | 827 | if (value == null) return oldValue; 828 | 829 | value = parseValue(value,oldValue) - oldValue; 830 | 831 | if (oldValue != value) { 832 | 833 | target.rotate(value); 834 | this.shapeEditor.update(); 835 | this.triggerChange(); 836 | } 837 | 838 | return this; 839 | }; 840 | 841 | /** 842 | * Get or set css property 843 | * @param {string} prop name of css property 844 | * @param {string,number} value 845 | * @returns {number,string,JSYG.FullEditor} 846 | */ 847 | FullEditor.prototype.css = function(prop,value) { 848 | 849 | if (JSYG.isPlainObject(prop)) { 850 | for (var n in prop) this.css(n,prop[n]); 851 | return this; 852 | } 853 | 854 | var target = this.target(), 855 | oldValue = target && target.css(prop); 856 | 857 | if (!target) return (value == null) ? null : this; 858 | 859 | if (value == null) return oldValue; 860 | 861 | value = parseValue(value,oldValue); 862 | 863 | if (oldValue != value) { 864 | 865 | target.css(prop,value); 866 | this.shapeEditor.update(); 867 | this.triggerChange(); 868 | } 869 | 870 | return this; 871 | }; 872 | 873 | /** 874 | * Trigger change event 875 | * @returns {JSYG.FullEditor} 876 | */ 877 | FullEditor.prototype.triggerChange = function() { 878 | 879 | this.undoRedo.saveState(); 880 | 881 | this.trigger("change", this, this.getDocument() ); 882 | 883 | return this; 884 | }; 885 | 886 | FullEditor.prototype._insertFrame = function() { 887 | 888 | var mainFrame = this.zoomAndPan.innerFrame, 889 | content = new JSYG(mainFrame).children().detach(); 890 | 891 | this._frameShadow = new JSYG("") 892 | .attr({x:2,y:2}) 893 | .addClass("jsyg-doc-shadow") 894 | .appendTo(mainFrame)[0]; 895 | 896 | this._frame = new JSYG("") 897 | .attr({x:0,y:0}) 898 | .addClass("jsyg-doc-frame") 899 | .appendTo(mainFrame)[0]; 900 | 901 | this.containerDoc = new JSYG("") 902 | .attr("id",this.idContainer) 903 | .append(content) 904 | .appendTo(mainFrame) 905 | [0]; 906 | 907 | this._adjustSize(); 908 | 909 | return this; 910 | }; 911 | 912 | /** 913 | * @property {string} cursorDrawing name of css cursor when drawing is active 914 | */ 915 | FullEditor.prototype.cursorDrawing = "copy"; 916 | 917 | FullEditor.prototype._removeFrame = function() { 918 | 919 | var container = new JSYG(this.containerDoc), 920 | content = container.children(); 921 | 922 | new JSYG(this._frame).remove(); 923 | new JSYG(this._frameShadow).remove(); 924 | container.remove(); 925 | 926 | content.appendTo(this.zoomAndPan.innerFrame); 927 | 928 | return this; 929 | }; 930 | 931 | FullEditor.prototype._initShapeDrawer = function() { 932 | 933 | this.pathDrawer = this._initDrawer(new PathDrawer); 934 | this.polylineDrawer = this._initDrawer(new PolylineDrawer); 935 | this.shapeDrawer = this._initDrawer(new ShapeDrawer); 936 | 937 | return this; 938 | }; 939 | 940 | FullEditor.prototype._initDrawer = function(drawer) { 941 | 942 | var that = this; 943 | 944 | drawer.on({ 945 | 946 | draw : function(e) { that.trigger("draw",that,e,this); }, 947 | 948 | end : function(e) { 949 | 950 | if (!this.parentNode) return; 951 | 952 | that.trigger("insert",that,e,this); 953 | 954 | that.triggerChange(); 955 | 956 | if (that.autoEnableSelection) that.shapeEditor.target(this).show(); 957 | } 958 | }); 959 | 960 | return drawer; 961 | }; 962 | 963 | FullEditor.prototype._setCursorDrawing = function() { 964 | 965 | if (this.cursorDrawing) this.zoomAndPan.node.style.cursor = this.cursorDrawing; 966 | }; 967 | 968 | FullEditor.prototype._removeCursorDrawing = function() { 969 | 970 | if (this.cursorDrawing) this.zoomAndPan.node.style.cursor = null; 971 | }; 972 | 973 | /** 974 | * @property {object} shapeDrawerModel dom node to clone when starting drawing 975 | */ 976 | Object.defineProperty(FullEditor.prototype,"shapeDrawerModel",{ 977 | 978 | get:function() { 979 | return this._shapeDrawerModel; 980 | }, 981 | 982 | set:function(value) { 983 | 984 | var jNode = new JSYG(value); 985 | 986 | if (jNode.length != 1) throw new Error("Shape model incorrect"); 987 | 988 | if (JSYG.svgShapes.indexOf(jNode.getTag()) == -1) 989 | throw new Error(jNode.getTag()+" is not a svg shape"); 990 | 991 | this._shapeDrawerModel = jNode[0]; 992 | } 993 | }); 994 | 995 | /** 996 | * Draw one shape 997 | * @param {type} modele 998 | * @returns {Promise} 999 | */ 1000 | FullEditor.prototype.drawShape = function(modele) { 1001 | 1002 | var that = this; 1003 | 1004 | return new Promise(function(resolve,reject) { 1005 | 1006 | that.enableShapeDrawer(modele,function() { 1007 | 1008 | var target = that.target(); 1009 | 1010 | that.disableShapeDrawer(); 1011 | 1012 | if (target) resolve(target[0]); 1013 | else reject(new Error("No shape was drawn")); 1014 | }); 1015 | }); 1016 | } 1017 | 1018 | 1019 | FullEditor.prototype.enableShapeDrawer = function(modele,_callback) { 1020 | 1021 | var frame = new JSYG(this.zoomAndPan.innerFrame), 1022 | that = this; 1023 | 1024 | this.disableEdition(); 1025 | 1026 | if (modele) this.shapeDrawerModel = modele; 1027 | 1028 | function onmousedown(e) { 1029 | 1030 | if (that.pathDrawer.inProgress || that.polylineDrawer.inProgress || that.shapeDrawer.inProgress || e.which != 1) return; 1031 | 1032 | e.preventDefault(); 1033 | 1034 | var modele = that.shapeDrawerModel; 1035 | if (!modele) throw new Error("You must define a model"); 1036 | 1037 | var shape = new JSYG(modele).clone().appendTo( that._getLayerSelector() ); 1038 | var tag = shape.getTag(); 1039 | var drawer; 1040 | 1041 | switch(tag) { 1042 | 1043 | case "polyline": case "polygon" : drawer = that.polylineDrawer; break; 1044 | 1045 | case "path" : drawer = that.pathDrawer; break; 1046 | 1047 | default : drawer = that.shapeDrawer; break; 1048 | } 1049 | 1050 | drawer.area = frame; 1051 | drawer.draw(shape,e); 1052 | 1053 | if (_callback) drawer.one("end",_callback); 1054 | } 1055 | 1056 | frame.on("mousedown",onmousedown).data("enableDrawingShape",onmousedown); 1057 | 1058 | this._setCursorDrawing(); 1059 | 1060 | return this; 1061 | }; 1062 | 1063 | FullEditor.prototype.disableShapeDrawer = function() { 1064 | 1065 | var frame = new JSYG(this.zoomAndPan.innerFrame), 1066 | fct = frame.data("enableDrawingShape"); 1067 | 1068 | if (!fct) return this; 1069 | 1070 | frame.off("mousedown",fct); 1071 | 1072 | this._removeCursorDrawing(); 1073 | 1074 | return this; 1075 | }; 1076 | 1077 | FullEditor.prototype.autoEnableSelection = true; 1078 | 1079 | Object.defineProperty(FullEditor.prototype,"insertElementModel",{ 1080 | 1081 | get:function() { 1082 | return this._insertElementModel; 1083 | }, 1084 | 1085 | set:function(value) { 1086 | 1087 | var jNode = new JSYG(value); 1088 | 1089 | if (jNode.length != 1) throw new Error("element model incorrect"); 1090 | 1091 | if (JSYG.svgGraphics.indexOf(jNode.getTag()) == -1) 1092 | throw new Error(jNode.getTag()+" is not a svg graphic element"); 1093 | 1094 | this._insertElementModel = jNode[0]; 1095 | } 1096 | }); 1097 | 1098 | FullEditor.prototype.is = function(type,_elmt) { 1099 | 1100 | _elmt = _elmt || this.target(); 1101 | 1102 | var list = "svg"+JSYG.ucfirst(type)+"s"; 1103 | var types = ["container","graphic","shape","text"]; 1104 | 1105 | if (types.indexOf(type) == -1) throw new Error(type+" : type incorrect ("+types+" required)"); 1106 | 1107 | return JSYG[list].indexOf( JSYG(_elmt).getTag() ) != -1; 1108 | }; 1109 | 1110 | FullEditor.prototype.mouseInsertElement = function(modele) { 1111 | 1112 | var that = this; 1113 | 1114 | return new Promise(function(resolve) { 1115 | 1116 | that.enableInsertElement(modele,function() { 1117 | 1118 | var target = that.target(); 1119 | 1120 | that.disableInsertElement(); 1121 | 1122 | if (target) resolve(target[0]); 1123 | else reject(new Error("No element inserted")); 1124 | }); 1125 | }); 1126 | } 1127 | 1128 | FullEditor.prototype.enableInsertElement = function(modele,_callback) { 1129 | 1130 | var frame = new JSYG(this.zoomAndPan.innerFrame), 1131 | that = this; 1132 | 1133 | this.disableEdition(); 1134 | 1135 | if (modele) this.insertElementModel = modele; 1136 | 1137 | function onmousedown(e) { 1138 | 1139 | if (e.which != 1) return; 1140 | 1141 | e.preventDefault(); 1142 | 1143 | var modele = that.insertElementModel; 1144 | if (!modele) throw new Error("You must define a model"); 1145 | 1146 | var shape = new JSYG(modele).clone(), 1147 | isText = JSYG.svgTexts.indexOf(shape.getTag()) !== -1; 1148 | 1149 | that.insertElement(shape,e,isText); 1150 | 1151 | if (that.autoEnableSelection) { 1152 | 1153 | new JSYG(that.node).one('mouseup',function() { 1154 | 1155 | that.shapeEditor.target(shape); 1156 | 1157 | if (that.editText && isText) { 1158 | that.textEditor.target(shape).show(); 1159 | that.textEditor.one("validate",_callback); 1160 | } 1161 | else { 1162 | that.shapeEditor.show(); 1163 | if (_callback) _callback(); 1164 | } 1165 | 1166 | }); 1167 | } 1168 | } 1169 | 1170 | frame.on("mousedown",onmousedown).data("enableInsertElement",onmousedown); 1171 | 1172 | this._setCursorDrawing(); 1173 | 1174 | return this; 1175 | }; 1176 | 1177 | FullEditor.prototype.disableInsertElement = function() { 1178 | 1179 | var frame = new JSYG(this.zoomAndPan.innerFrame), 1180 | fct = frame.data("enableInsertElement"); 1181 | 1182 | if (!fct) return this; 1183 | 1184 | frame.off("mousedown",fct); 1185 | 1186 | this._removeCursorDrawing(); 1187 | 1188 | return this; 1189 | }; 1190 | 1191 | FullEditor.prototype.marqueeZoom = function(opt) { 1192 | 1193 | var that = this; 1194 | 1195 | return new Promise(function(resolve) { 1196 | 1197 | that.enableMarqueeZoom(opt,function() { 1198 | that.disableMarqueeZoom(); 1199 | resolve(); 1200 | }); 1201 | }); 1202 | }; 1203 | 1204 | FullEditor.prototype.disableMarqueeZoom = function() { 1205 | 1206 | this.zoomAndPan.marqueeZoom.disable(); 1207 | 1208 | return this; 1209 | }; 1210 | 1211 | FullEditor.prototype.enableMarqueeZoom = function(opt,_callback) { 1212 | 1213 | if (this.zoomAndPan.marqueeZoom.enabled && !opt) return this; 1214 | 1215 | this.disableEdition(); 1216 | 1217 | this.zoomAndPan.marqueeZoom.enable(opt); 1218 | 1219 | if (_callback) this.zoomAndPan.marqueeZoom.one("end",_callback); 1220 | 1221 | return this; 1222 | }; 1223 | 1224 | FullEditor.prototype.zoom = function(percent) { 1225 | 1226 | this.zoomAndPan.scale( 1 + (percent/100) ); 1227 | 1228 | this.trigger("zoom",this,this.getDocument()); 1229 | 1230 | return this; 1231 | }; 1232 | 1233 | FullEditor.prototype.zoomTo = function(percentage) { 1234 | 1235 | if (percentage == "canvas") this.zoomAndPan.fitToCanvas().scale(0.95); 1236 | else if (JSYG.isNumeric(percentage)) this.zoomAndPan.scaleTo( percentage/100 ); 1237 | else throw new Error("argument must be numeric or 'canvas' string"); 1238 | 1239 | this.trigger("zoom",this,this.getDocument()); 1240 | 1241 | return this; 1242 | }; 1243 | 1244 | FullEditor.prototype.fitToDoc = function() { 1245 | 1246 | var dim = new JSYG(this.getDocument()).getDim("screen"), 1247 | overflow = this.zoomAndPan.overflow; 1248 | 1249 | this.zoomAndPan.size({ 1250 | width : dim.width + (overflow!="hidden" ? 10 : 0), 1251 | height : dim.height + (overflow!="hidden" ? 10 : 0) 1252 | }); 1253 | 1254 | return this; 1255 | }; 1256 | 1257 | 1258 | FullEditor.prototype._initZoomAndPan = function() { 1259 | 1260 | var that = this; 1261 | 1262 | this.zoomAndPan = new ZoomAndPan(); 1263 | this.zoomAndPan.overflow = "auto"; 1264 | this.zoomAndPan.scaleMin = 0; 1265 | 1266 | this.zoomAndPan.resizable.keepViewBox = false; 1267 | this.zoomAndPan.resizable.keepRatio = false; 1268 | 1269 | this.zoomAndPan.mouseWheelZoom.key = "ctrl"; 1270 | 1271 | this.zoomAndPan.on("change",function() { 1272 | that._updateBoundingBoxes(); 1273 | that.shapeEditor.update(); 1274 | that.textEditor.update(); 1275 | }); 1276 | 1277 | return this; 1278 | }; 1279 | 1280 | 1281 | FullEditor.prototype._initShapeEditor = function() { 1282 | 1283 | var editor = new Editor(); 1284 | var that = this; 1285 | 1286 | editor.selection.multiple = true; 1287 | 1288 | new JSYG(editor.container).on("dblclick",function(e) { 1289 | 1290 | var target = editor.target(); 1291 | 1292 | if (!that.editText || JSYG.svgTexts.indexOf( target.getTag() ) == -1) return; 1293 | 1294 | that.textEditor.target(target).show(); 1295 | that.textEditor.cursor.setFromPos(e); 1296 | }); 1297 | 1298 | editor.selection.on("beforedeselect beforedrag",function(e) { 1299 | 1300 | if (e.target == that.textEditor.container || new JSYG(e.target).isChildOf(that.textEditor.container)) return false; 1301 | }); 1302 | 1303 | editor.on({ 1304 | 1305 | show : function() { 1306 | that.textEditor.hide(); 1307 | }, 1308 | 1309 | change : this.triggerChange, 1310 | 1311 | drag : function(e) { 1312 | that.trigger("drag", that, e, editor._target); 1313 | }, 1314 | 1315 | changetarget : function() { 1316 | that.trigger("changetarget",that,editor._target); 1317 | } 1318 | }); 1319 | 1320 | //editor.ctrlsDrag.bounds = 0; 1321 | //editor.ctrlsResize.bounds = 0; 1322 | 1323 | this.shapeEditor = editor; 1324 | 1325 | return this; 1326 | }; 1327 | 1328 | FullEditor.prototype._initTextEditor = function() { 1329 | 1330 | var that = this; 1331 | 1332 | this.textEditor = new TextEditor(); 1333 | 1334 | this.textEditor.on({ 1335 | show : function() { 1336 | that.shapeEditor.hide(); 1337 | }, 1338 | hide : function() { 1339 | var target = that.textEditor.target(); 1340 | if (!target.text()) target.remove(); 1341 | else that.shapeEditor.target(target).show(); 1342 | }, 1343 | validate : function() { 1344 | that.triggerChange(); 1345 | } 1346 | }); 1347 | 1348 | return this; 1349 | }; 1350 | 1351 | FullEditor.prototype.setNode = function() { 1352 | 1353 | JSYG.StdConstruct.prototype.setNode.apply(this,arguments); 1354 | 1355 | this.zoomAndPan.setNode(this.node); 1356 | 1357 | this.shapeEditor.setNode(this.node); 1358 | 1359 | this.textEditor.setNode(this.node); 1360 | 1361 | return this; 1362 | }; 1363 | 1364 | FullEditor.prototype._getDocumentSelector = function() { 1365 | 1366 | return "#" + this.idContainer + " > svg "; 1367 | }; 1368 | 1369 | FullEditor.prototype._editableShapes = "*"; 1370 | 1371 | Object.defineProperty(FullEditor.prototype,'editableShapes',{ 1372 | 1373 | get : function() { 1374 | return this._editableShapes; 1375 | }, 1376 | 1377 | set : function(value) { 1378 | 1379 | this._editableShapes = value; 1380 | this.shapeEditor.list = this._getLayerSelector()+value; 1381 | } 1382 | }); 1383 | 1384 | 1385 | FullEditor.prototype.enableMousePan = function(opt) { 1386 | 1387 | if (!this.zoomAndPan.mousePan.enabled) { 1388 | 1389 | this.disableEdition(); 1390 | 1391 | this.zoomAndPan.mousePan.enable(opt); 1392 | } 1393 | 1394 | return this; 1395 | }; 1396 | 1397 | FullEditor.prototype.disableMousePan = function() { 1398 | 1399 | if (this.zoomAndPan.mousePan.enabled) { 1400 | 1401 | this.zoomAndPan.mousePan.disable(); 1402 | } 1403 | 1404 | return this; 1405 | }; 1406 | 1407 | FullEditor.prototype.enableMouseWheelZoom = function(opt) { 1408 | 1409 | if (!this.zoomAndPan.mouseWheelZoom.enabled) { 1410 | 1411 | this.zoomAndPan.mouseWheelZoom.enable(opt); 1412 | } 1413 | 1414 | return this; 1415 | }; 1416 | 1417 | FullEditor.prototype.disableMouseWheelZoom = function() { 1418 | 1419 | if (this.zoomAndPan.mouseWheelZoom.enabled) { 1420 | 1421 | this.zoomAndPan.mouseWheelZoom.disable(); 1422 | } 1423 | 1424 | return this; 1425 | }; 1426 | 1427 | FullEditor.prototype.canMoveBackwards = function() { 1428 | 1429 | var shapes = new JSYG(this.shapeEditor.list), 1430 | target = this.shapeEditor.target(); 1431 | 1432 | return shapes.index(target) > 0 && shapes.length > 1; 1433 | }; 1434 | 1435 | FullEditor.prototype.canMoveForwards = function() { 1436 | 1437 | var shapes = new JSYG(this.shapeEditor.list), 1438 | target = this.shapeEditor.target(); 1439 | 1440 | return shapes.index(target) < shapes.length-1 && shapes.length > 1; 1441 | }; 1442 | 1443 | FullEditor.prototype.isGroup = function() { 1444 | 1445 | return this.shapeEditor.isGroup(); 1446 | }; 1447 | 1448 | Object.defineProperty(FullEditor.prototype,'overflow',{ 1449 | 1450 | get : function() { return this.zoomAndPan.overflow; }, 1451 | 1452 | set : function(value) { 1453 | 1454 | var displayShapeEditor = this.shapeEditor.display, 1455 | displaytextEditor = this.textEditor.display; 1456 | 1457 | if (displayShapeEditor) this.shapeEditor.hide(); 1458 | if (displaytextEditor) this.textEditor.hide(); 1459 | 1460 | this.zoomAndPan.overflow = value; 1461 | 1462 | if (displayShapeEditor) this.shapeEditor.show(); 1463 | if (displaytextEditor) this.textEditor.show(); 1464 | } 1465 | }); 1466 | 1467 | FullEditor.prototype.enable = function(opt) { 1468 | 1469 | this.disable(); 1470 | 1471 | if (opt) this.set(opt); 1472 | 1473 | if (!this.node) throw new Error("node is not defined"); 1474 | 1475 | this.zoomAndPan.enable(); 1476 | 1477 | this._insertFrame(); 1478 | 1479 | //on force les valeurs pour exécuter les fonctions définies dans Object.defineProperty 1480 | if (this._editPathCtrlPoints) this._editPathCtrlPoints = true; 1481 | if (this._resizable) this._resizable = true; 1482 | this.editableShapes = this.editableShapes; 1483 | 1484 | this.shapeEditor.enableCtrls('drag','resize','rotate','mainPoints'); 1485 | 1486 | if (this.autoEnableSelection) this.shapeEditor.enable(); 1487 | 1488 | this.enableKeyShortCuts(); 1489 | 1490 | this.enabled = true; 1491 | 1492 | return this; 1493 | }; 1494 | 1495 | FullEditor.prototype.disable = function() { 1496 | 1497 | this.disableEdition(); 1498 | 1499 | this._removeFrame(); 1500 | 1501 | this.zoomAndPan.disable(); 1502 | 1503 | this.undoRedo.disable(); 1504 | 1505 | this.disableKeyShortCuts(); 1506 | 1507 | this.enabled = false; 1508 | 1509 | return this; 1510 | }; 1511 | 1512 | /** 1513 | * Aligne les éléments sélectionnés 1514 | * @param {String} type (top,middle,bottom,left,center,right) 1515 | * @returns {undefined} 1516 | */ 1517 | FullEditor.prototype.align = function(type) { 1518 | 1519 | this.shapeEditor.align(type); 1520 | 1521 | return this; 1522 | }; 1523 | 1524 | FullEditor.prototype.target = function(value) { 1525 | 1526 | if (value == null) { 1527 | 1528 | if (this.textEditor.display) return this.textEditor.target(); 1529 | else return this.shapeEditor.target(); 1530 | } 1531 | else { 1532 | 1533 | this.shapeEditor.target( new JSYG(this.getDocument()).find(value) ).show(); 1534 | } 1535 | 1536 | return this; 1537 | }; 1538 | 1539 | FullEditor.prototype.editTextElmt = function(elmt) { 1540 | 1541 | if (elmt == null) elmt = this.target(); 1542 | 1543 | this.textEditor.target(elmt).show(); 1544 | 1545 | return this; 1546 | }; 1547 | 1548 | FullEditor.prototype.dimDocument = function(dim) { 1549 | 1550 | var doc = new JSYG( this.getDocument() ); 1551 | var oldDim = doc.getDim(); 1552 | 1553 | if (dim == null) return oldDim; 1554 | 1555 | if (dim.width && dim.width != oldDim.width || dim.height && dim.height != oldDim.height) { 1556 | 1557 | doc.setDim(dim); 1558 | 1559 | this.triggerChange(); 1560 | 1561 | this._adjustSize(); 1562 | } 1563 | 1564 | return this; 1565 | }; 1566 | 1567 | FullEditor.prototype.isMultiSelection = function() { 1568 | 1569 | return this.shapeEditor.isMultiSelection(); 1570 | }; 1571 | 1572 | FullEditor.prototype._adjustSize = function() { 1573 | 1574 | var contenu = new JSYG( this.getDocument() ), 1575 | dim = contenu.length && contenu.getDim() || {width:0, height:0}; 1576 | 1577 | new JSYG(this._frameShadow).add(this._frame).attr({ 1578 | width:dim.width, 1579 | height:dim.height 1580 | }); 1581 | 1582 | if (dim.width && dim.height) this.zoomTo('canvas'); 1583 | 1584 | if (!this.shapeEditor.ctrlsDrag.options) this.shapeEditor.ctrlsDrag.options = {}; 1585 | if (!this.shapeEditor.ctrlsResize.options) this.shapeEditor.ctrlsResize.options = {}; 1586 | 1587 | this.shapeEditor.ctrlsDrag.options.guides = { 1588 | list : [{x:0},{x:dim.width},{y:0},{y:dim.height}] 1589 | }; 1590 | 1591 | this.shapeEditor.ctrlsResize.options.stepsX = { 1592 | list : [0,dim.width] 1593 | }; 1594 | 1595 | this.shapeEditor.ctrlsResize.options.stepsY = { 1596 | list : [0,dim.height] 1597 | }; 1598 | 1599 | return this; 1600 | }; 1601 | 1602 | FullEditor.prototype.createImage = function(src) { 1603 | 1604 | var image = new JSYG('').attr('href',src), 1605 | that = this; 1606 | 1607 | return new Promise(function(resolve,reject) { 1608 | 1609 | var img = new Image(); 1610 | 1611 | img.onload = function() { 1612 | 1613 | var dimDoc = JSYG(that.getDocument()).viewBox(), 1614 | height = this.height, 1615 | width = this.width; 1616 | 1617 | if (width > dimDoc.width) { 1618 | height = height * dimDoc.width / width; 1619 | width = dimDoc.width; 1620 | } 1621 | 1622 | if (height > dimDoc.height) { 1623 | width = width * dimDoc.height / height; 1624 | height = dimDoc.height; 1625 | } 1626 | 1627 | image.attr({width:width,height:height}); 1628 | 1629 | resolve(image[0]); 1630 | }; 1631 | 1632 | img.onerror = reject; 1633 | 1634 | img.src = src; 1635 | }); 1636 | }; 1637 | 1638 | FullEditor.prototype.insertElement = function(elmt,pos,_preventEvent) { 1639 | 1640 | var textNode; 1641 | 1642 | elmt = new JSYG(elmt); 1643 | 1644 | elmt.appendTo( this._getLayerSelector() ); 1645 | 1646 | if (JSYG.svgTexts.indexOf(elmt.getTag())!=-1 && !elmt.text()) { 1647 | textNode = document.createTextNode("I"); 1648 | elmt.append(textNode); 1649 | } 1650 | 1651 | if (pos instanceof JSYG.Event) elmt.setCenter( elmt.getCursorPos(pos) ); 1652 | else { 1653 | 1654 | elmt.setDim({ 1655 | x : pos && pos.x || 0, 1656 | y : pos && pos.y || 0 1657 | }); 1658 | } 1659 | 1660 | if (textNode) new JSYG(textNode).remove(); 1661 | 1662 | if (!_preventEvent) { 1663 | 1664 | this.trigger("insert", this, this.getDocument(), elmt ); 1665 | this.triggerChange(); 1666 | } 1667 | 1668 | return this; 1669 | }; 1670 | 1671 | function stopEvents(e) { 1672 | e.stopPropagation(); 1673 | e.preventDefault(); 1674 | } 1675 | 1676 | FullEditor.prototype.lang = "en"; 1677 | 1678 | FullEditor.prototype.importSVGAs = "image"; 1679 | 1680 | FullEditor.prototype.enableDropFiles = function() { 1681 | 1682 | var that = this; 1683 | 1684 | var fcts = { 1685 | 1686 | dragenter : stopEvents, 1687 | dragover : stopEvents, 1688 | 1689 | drop : function(e) { 1690 | 1691 | stopEvents(e); 1692 | 1693 | var dt = e.originalEvent.dataTransfer; 1694 | var file, str; 1695 | 1696 | if (!dt) return; 1697 | 1698 | if (dt.files && dt.files.length) { 1699 | 1700 | file = dt.files[0]; 1701 | 1702 | if (/svg/i.test(file.type) && that.importSVGAs.toLowerCase() == "svg") that.insertSVGFile(file,e); 1703 | else that.insertImageFile(file,e); 1704 | 1705 | } else { 1706 | 1707 | str = dt.getData("text") 1708 | 1709 | if (str) { 1710 | 1711 | that.importImage(str) 1712 | .then(function(img) { that.insertElement(img,e); that.target(img); }) 1713 | .catch(function() {}) 1714 | 1715 | } 1716 | 1717 | } 1718 | } 1719 | } 1720 | 1721 | JSYG(this.zoomAndPan.innerFrame).on(fcts); 1722 | 1723 | this.disableDropFiles = function() { 1724 | 1725 | JSYG(this.zoomAndPan.innerFrame).off(fcts); 1726 | }; 1727 | 1728 | return this; 1729 | }; 1730 | 1731 | FullEditor.prototype.disableDropFiles = function() { return this; }; 1732 | 1733 | FullEditor.prototype.insertImageFile = function(file,e) { 1734 | 1735 | var that = this; 1736 | 1737 | return this.importImage(file) 1738 | .then(function(img) { 1739 | that.insertElement(img,e); 1740 | that.shapeEditor.target(img).show(); 1741 | }); 1742 | }; 1743 | 1744 | FullEditor.prototype.insertSVGFile = function(file,e) { 1745 | 1746 | var that = this; 1747 | 1748 | return this.readFile(file,"text") 1749 | .then(JSYG.parseSVG) 1750 | .then(function(svg) { 1751 | that.insertElement(svg,e); 1752 | that.shapeEditor.target(svg).show(); 1753 | }); 1754 | }; 1755 | 1756 | FullEditor.prototype.importImage = function(arg) { 1757 | 1758 | var promise; 1759 | 1760 | if (arg instanceof File) { 1761 | 1762 | if (!arg.type.match(/image/)) return Promise.reject(new TypeError(arg.name + " is not an image file")); 1763 | 1764 | promise = this.readFile(arg,'dataURL'); 1765 | } 1766 | else { 1767 | 1768 | if (arg.src) arg = arg.src; //DOMElement 1769 | else if (arg instanceof jQuery) { 1770 | 1771 | arg = JSYG(arg); 1772 | arg = arg.attr( arg.isSVG() ? 'href' : 'src' ); 1773 | 1774 | if (!arg) throw new TypeError("no src/href attribute found"); 1775 | } 1776 | else if (typeof arg != "string") throw new TypeError("argument incorrect"); //URL or dataURL 1777 | 1778 | promise = Promise.resolve(arg); 1779 | } 1780 | 1781 | return promise.then(this.createImage); 1782 | }; 1783 | 1784 | FullEditor.prototype.chooseFile = function() { 1785 | 1786 | var that = this; 1787 | 1788 | return new Promise(function(resolve,reject) { 1789 | 1790 | JSYG("").attr("type","file").on("change",function() { 1791 | 1792 | resolve(this.files[0]); 1793 | }) 1794 | .trigger("click"); 1795 | }); 1796 | }; 1797 | 1798 | FullEditor.prototype.loadImageAsDoc = function(arg) { 1799 | 1800 | var that = this; 1801 | 1802 | return this.importImage(arg).then(function(img) { 1803 | 1804 | var dim; 1805 | 1806 | that.insertElement(img); 1807 | 1808 | dim = JSYG(img).getDim(); 1809 | 1810 | that.newDocument(dim.width,dim.height); 1811 | that.insertElement(img); 1812 | that.addLayer(); 1813 | 1814 | that.undoRedo.clear(); 1815 | 1816 | return img; 1817 | }); 1818 | }; 1819 | 1820 | /** 1821 | * Load a document from a file, an url, a xml string or a xml node 1822 | * @param {File, string, DOMElement} arg 1823 | * @returns {Promise} 1824 | */ 1825 | FullEditor.prototype.load = function(arg) { 1826 | 1827 | if (arg instanceof File) return this.loadFile(arg); 1828 | else if (typeof arg == "string") { 1829 | if (arg.indexOf("').setDim( {width:width,height:height} ); 1969 | 1970 | return this.loadXML(svg); 1971 | }; 1972 | 1973 | FullEditor.prototype._updateBoundingBoxes = function() { 1974 | 1975 | new JSYG(this.shapeEditor.list).each(function() { 1976 | var $this = new JSYG(this); 1977 | if ($this.boundingBox("get","display")) $this.boundingBox("update"); 1978 | }); 1979 | }; 1980 | 1981 | FullEditor.prototype._clearBoundingBoxes = function() { 1982 | 1983 | new JSYG(this.shapeEditor.list).boundingBox("hide"); 1984 | }; 1985 | 1986 | /** 1987 | * Convert document to a canvas element 1988 | * @returns {Promise} 1989 | */ 1990 | FullEditor.prototype.toCanvas = function() { 1991 | 1992 | return new JSYG(this.getDocument()).toCanvas(); 1993 | }; 1994 | 1995 | /** 1996 | * Convert document to a SVG string (keep links) 1997 | * @param {object} opt options (for the moment only "standalone" as boolean, to converts links to dataURLs) 1998 | * @returns {Promise} 1999 | * @example fullEditor.toSVGString({standalone:true}) 2000 | */ 2001 | FullEditor.prototype.toSVGString = function(opt) { 2002 | 2003 | return new JSYG(this.getDocument()).toSVGString(opt && opt.standalone); 2004 | }; 2005 | 2006 | /** 2007 | * Convert document to a SVG data url 2008 | * @returns {Promise} 2009 | */ 2010 | FullEditor.prototype.toSVGDataURL = function() { 2011 | 2012 | return new JSYG(this.getDocument()).toDataURL(true); 2013 | }; 2014 | 2015 | /** 2016 | * Convert document to a PNG data url 2017 | * @returns {Promise} 2018 | */ 2019 | FullEditor.prototype.toPNGDataURL = function(format) { 2020 | 2021 | return this.toCanvas().then(function(canvas) { 2022 | 2023 | return canvas.toDataURL(); 2024 | }); 2025 | }; 2026 | 2027 | FullEditor.prototype._checkExportFormat = function(format) { 2028 | 2029 | var exportFormats = ['svg','png']; 2030 | 2031 | if (exportFormats.indexOf(format) == -1) throw new Error(format+" : incorrect format ("+exportFormats.join(' or ')+" required)"); 2032 | }; 2033 | 2034 | /** 2035 | * Convert document to data URL 2036 | * @param {string} format "svg" or "png" 2037 | * @returns {Promise} 2038 | */ 2039 | FullEditor.prototype.toDataURL = function(format) { 2040 | 2041 | if (!format) format = 'svg'; 2042 | 2043 | this._checkExportFormat(format); 2044 | 2045 | var method = "to"+format.toUpperCase()+"DataURL"; 2046 | 2047 | return this[method](); 2048 | }; 2049 | 2050 | /** 2051 | * Print document 2052 | * @returns {Promise} 2053 | */ 2054 | FullEditor.prototype.print = function() { 2055 | 2056 | return this.toSVGDataURL().then(function(url) { 2057 | 2058 | return new Promise(function(resolve) { 2059 | var win = window.open(url); 2060 | win.onload = function() { win.print(); resolve(); }; 2061 | }) 2062 | }); 2063 | }; 2064 | 2065 | /** 2066 | * Download document as PNG 2067 | * @returns {Promise} 2068 | */ 2069 | FullEditor.prototype.downloadPNG = function() { 2070 | 2071 | return this.download("png"); 2072 | }; 2073 | 2074 | /** 2075 | * Download document as SVG 2076 | * @returns {Promise} 2077 | */ 2078 | FullEditor.prototype.downloadSVG = function() { 2079 | 2080 | return this.download("svg"); 2081 | }; 2082 | 2083 | /** 2084 | * Download document 2085 | * @param {string} format "png" or "svg" 2086 | * @returns {JSYG.FullEditor} 2087 | */ 2088 | FullEditor.prototype.download = function(format) { 2089 | 2090 | if (!format) format = 'svg'; 2091 | 2092 | return this.toDataURL(format).then(function(url) { 2093 | 2094 | var a = new JSYG('').attr({ 2095 | href:url, 2096 | download:"file."+format 2097 | }).appendTo('body'); 2098 | 2099 | a[0].click(); 2100 | a.remove(); 2101 | }); 2102 | }; 2103 | 2104 | /** 2105 | * Remove selection 2106 | * @returns {JSYG.FullEditor} 2107 | */ 2108 | FullEditor.prototype.remove = function() { 2109 | 2110 | if (!this.shapeEditor.display) return this; 2111 | 2112 | var target = this.shapeEditor.target(); 2113 | 2114 | this.shapeEditor.hide(); 2115 | 2116 | this._clearBoundingBoxes(); 2117 | 2118 | target.remove(); 2119 | 2120 | this.trigger("remove", this, this.getDocument(), target); 2121 | 2122 | this.triggerChange(); 2123 | 2124 | return this; 2125 | }; 2126 | 2127 | /** 2128 | * Group selected elements 2129 | * @returns {JSYG.FullEditor} 2130 | */ 2131 | FullEditor.prototype.group = function() { 2132 | 2133 | this.shapeEditor.group(); 2134 | 2135 | this.triggerChange(); 2136 | 2137 | return this; 2138 | }; 2139 | 2140 | /** 2141 | * Ungroup selection 2142 | * @returns {JSYG.FullEditor} 2143 | */ 2144 | FullEditor.prototype.ungroup = function() { 2145 | 2146 | this.shapeEditor.ungroup(); 2147 | 2148 | this.triggerChange(); 2149 | 2150 | return this; 2151 | }; 2152 | 2153 | /** 2154 | * Center selected elements 2155 | * @param {string} orientation "vertical" or "horizontal" 2156 | * @returns {JSYG.FullEditor} 2157 | */ 2158 | FullEditor.prototype.center = function(orientation) { 2159 | 2160 | var doc = this.getDocument(), 2161 | dimDoc = new JSYG(doc).getDim(), 2162 | target = this.target(), 2163 | dim = target.getDim(doc), 2164 | isVerti = orientation.toLowerCase().indexOf("verti") == 0, 2165 | newX = (dimDoc.width - dim.width) / 2, 2166 | newY = (dimDoc.height - dim.height) /2; 2167 | 2168 | if (isVerti && dim.x != newX) target.setDim({x:newX}); 2169 | else if (!isVerti && dim.y != newY) target.setDim({y:newY}); 2170 | else return this; 2171 | 2172 | if (this.shapeEditor.display) this.shapeEditor.update(); 2173 | else if (this.textEditor.display) this.textEditor.update(); 2174 | 2175 | this.triggerChange(); 2176 | 2177 | return this; 2178 | }; 2179 | 2180 | /** 2181 | * Center selected elements vertically 2182 | * @returns {JSYG.FullEditor} 2183 | */ 2184 | FullEditor.prototype.centerVertically = function() { 2185 | 2186 | return this.center("vertically"); 2187 | }; 2188 | 2189 | /** 2190 | * Center selected elements horizontally 2191 | * @returns {JSYG.FullEditor} 2192 | */ 2193 | FullEditor.prototype.centerHorizontally = function() { 2194 | 2195 | return this.center("horizontally"); 2196 | }; 2197 | 2198 | 2199 | JSYG.FullEditor = FullEditor; 2200 | 2201 | return FullEditor; 2202 | 2203 | }); 2204 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ybochatay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSYG.FullEditor 2 | 3 | Provides a complete and very simple API to create your own svg online editor. UI is your concern. 4 | 5 | ### Demo 6 | 7 | [http://yannickbochatay.github.io/JSYG.FullEditor](http://yannickbochatay.github.io/JSYG.FullEditor/) 8 | 9 | ### Installation 10 | 11 | ```shell 12 | npm install jsyg-fulleditor 13 | ``` 14 | 15 | ### Example 16 | 17 | HTML 18 | 19 | ```html 20 | 21 | 22 | Shape : 23 | 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | Javascript 36 | 37 | ```javascript 38 | var svgEditor = new JSYG.FullEditor("#editor"); 39 | 40 | svgEditor.enable(); 41 | 42 | svgEditor.newDocument(600,600); 43 | 44 | $("[name=shape]").on("change",function() { 45 | svgEditor.shapeDrawerModel = '<'+this.value+'>'; 46 | }).trigger("change"); 47 | 48 | svgEditor.enableShapeDrawer(); 49 | 50 | $("#importImage").on("click",function() { 51 | svgEditor.chooseFile().then(svgEditor.insertImageFile); 52 | }); 53 | 54 | $("#download").on("click",function() { 55 | svgEditor.download("svg"); 56 | }); 57 | ``` 58 | 59 | ### Full example script 60 | 61 | 62 | 63 | ### API 64 | 65 | 66 | 67 | ##### Table of Contents 68 | 69 | * [registerKeyShortCut](#registerkeyshortcut) 70 | * [Parameters](#parameters) 71 | * [unregisterKeyShortCut](#unregisterkeyshortcut) 72 | * [Parameters](#parameters-1) 73 | * [selectAll](#selectall) 74 | * [deselectAll](#deselectall) 75 | * [enableKeyShortCuts](#enablekeyshortcuts) 76 | * [disableKeyShortCuts](#disablekeyshortcuts) 77 | * [getLayers](#getlayers) 78 | * [addLayer](#addlayer) 79 | * [removeLayer](#removelayer) 80 | * [getDocument](#getdocument) 81 | * [hideEditors](#hideeditors) 82 | * [enableSelection](#enableselection) 83 | * [disableSelection](#disableselection) 84 | * [disableInsertion](#disableinsertion) 85 | * [disableEdition](#disableedition) 86 | * [duplicate](#duplicate) 87 | * [dim](#dim) 88 | * [Parameters](#parameters-2) 89 | * [rotate](#rotate) 90 | * [Parameters](#parameters-3) 91 | * [css](#css) 92 | * [Parameters](#parameters-4) 93 | * [triggerChange](#triggerchange) 94 | * [cursorDrawing](#cursordrawing) 95 | * [Properties](#properties) 96 | * [drawShape](#drawshape) 97 | * [Parameters](#parameters-5) 98 | * [align](#align) 99 | * [Parameters](#parameters-6) 100 | * [load](#load) 101 | * [Parameters](#parameters-7) 102 | * [loadString](#loadstring) 103 | * [Parameters](#parameters-8) 104 | * [readFile](#readfile) 105 | * [Parameters](#parameters-9) 106 | * [loadFile](#loadfile) 107 | * [Parameters](#parameters-10) 108 | * [loadURL](#loadurl) 109 | * [Parameters](#parameters-11) 110 | * [loadXML](#loadxml) 111 | * [Parameters](#parameters-12) 112 | * [newDocument](#newdocument) 113 | * [Parameters](#parameters-13) 114 | * [toCanvas](#tocanvas) 115 | * [toSVGString](#tosvgstring) 116 | * [Parameters](#parameters-14) 117 | * [Examples](#examples) 118 | * [toSVGDataURL](#tosvgdataurl) 119 | * [toPNGDataURL](#topngdataurl) 120 | * [Parameters](#parameters-15) 121 | * [toDataURL](#todataurl) 122 | * [Parameters](#parameters-16) 123 | * [print](#print) 124 | * [downloadPNG](#downloadpng) 125 | * [downloadSVG](#downloadsvg) 126 | * [download](#download) 127 | * [Parameters](#parameters-17) 128 | * [remove](#remove) 129 | * [group](#group) 130 | * [ungroup](#ungroup) 131 | * [center](#center) 132 | * [Parameters](#parameters-18) 133 | * [centerVertically](#centervertically) 134 | * [centerHorizontally](#centerhorizontally) 135 | * [registerPlugin](#registerplugin) 136 | * [Parameters](#parameters-19) 137 | * [defineProperty](#defineproperty) 138 | * [Properties](#properties-1) 139 | * [defineProperty](#defineproperty-1) 140 | * [Properties](#properties-2) 141 | * [defineProperty](#defineproperty-2) 142 | * [Properties](#properties-3) 143 | * [defineProperty](#defineproperty-3) 144 | * [Properties](#properties-4) 145 | * [defineProperty](#defineproperty-4) 146 | * [Properties](#properties-5) 147 | * [defineProperty](#defineproperty-5) 148 | * [Properties](#properties-6) 149 | * [defineProperty](#defineproperty-6) 150 | * [Properties](#properties-7) 151 | * [defineProperty](#defineproperty-7) 152 | * [Properties](#properties-8) 153 | * [defineProperty](#defineproperty-8) 154 | * [Properties](#properties-9) 155 | * [defineProperty](#defineproperty-9) 156 | * [Properties](#properties-10) 157 | * [defineProperty](#defineproperty-10) 158 | * [Properties](#properties-11) 159 | * [defineProperty](#defineproperty-11) 160 | * [Properties](#properties-12) 161 | * [defineProperty](#defineproperty-12) 162 | * [Properties](#properties-13) 163 | 164 | #### registerKeyShortCut 165 | 166 | You can also pass an object with several key shortcuts as keys/values 167 | 168 | ##### Parameters 169 | 170 | * `key` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** jquery hotkeys syntax (example : "ctrl+i") 171 | * `fct` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** callback called when key or combination keys are pressed 172 | 173 | Returns **JSYG.FullEditor** 174 | 175 | #### unregisterKeyShortCut 176 | 177 | Unregister a key shortcut 178 | 179 | ##### Parameters 180 | 181 | * `key` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** jquery hotkeys syntax (example : "ctrl+i") 182 | 183 | Returns **JSYG.FullEditor** 184 | 185 | #### selectAll 186 | 187 | Select all editable elements in document 188 | 189 | Returns **JSYG.FullEditor** 190 | 191 | #### deselectAll 192 | 193 | Deselect all editable elements 194 | 195 | Returns **JSYG.FullEditor** 196 | 197 | #### enableKeyShortCuts 198 | 199 | * **See**: JSYG.prototype.registerKeyShortCut 200 | 201 | Enable all key shorcuts registered by registerKeyShortCut method 202 | 203 | Returns **JSYG.FullEditor** 204 | 205 | #### disableKeyShortCuts 206 | 207 | * **See**: JSYG.prototype.registerKeyShortCut 208 | 209 | Disable all key shorcuts registered by registerKeyShortCut method 210 | 211 | Returns **JSYG.FullEditor** 212 | 213 | #### getLayers 214 | 215 | Get all layers defined 216 | 217 | Returns **JSYG** 218 | 219 | #### addLayer 220 | 221 | Add and use a new layer 222 | 223 | Returns **JSYG.FullEditor** 224 | 225 | #### removeLayer 226 | 227 | Remove a layer 228 | 229 | Returns **JSYG.FullEditor** 230 | 231 | #### getDocument 232 | 233 | Get document as a DOM node 234 | 235 | Returns **[Element](https://developer.mozilla.org/docs/Web/API/Element)** 236 | 237 | #### hideEditors 238 | 239 | Hide shape and text editors 240 | 241 | Returns **JSYG.FullEditor** 242 | 243 | #### enableSelection 244 | 245 | Enable mouse pointer selection 246 | 247 | Returns **JSYG.FullEditor** 248 | 249 | #### disableSelection 250 | 251 | Disable mouse pointer selection 252 | 253 | Returns **JSYG.FullEditor** 254 | 255 | #### disableInsertion 256 | 257 | Disable mouse pointer insertion 258 | 259 | Returns **JSYG.FullEditor** 260 | 261 | #### disableEdition 262 | 263 | Enable edition functionalities 264 | 265 | Returns **JSYG.FullEditor** 266 | 267 | #### duplicate 268 | 269 | Duplicate selected element 270 | 271 | Returns **JSYG.FullEditor** 272 | 273 | #### dim 274 | 275 | You can also pass an object 276 | 277 | ##### Parameters 278 | 279 | * `prop` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** x, y , width or height 280 | * `value` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** 281 | 282 | #### rotate 283 | 284 | Rotate selected element 285 | 286 | ##### Parameters 287 | 288 | * `value` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** angle in degrees 289 | 290 | Returns **JSYG.FullEditor** 291 | 292 | #### css 293 | 294 | Get or set css property 295 | 296 | ##### Parameters 297 | 298 | * `prop` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** name of css property 299 | * `value` 300 | 301 | #### triggerChange 302 | 303 | Trigger change event 304 | 305 | Returns **JSYG.FullEditor** 306 | 307 | #### cursorDrawing 308 | 309 | ##### Properties 310 | 311 | * `cursorDrawing` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** name of css cursor when drawing is active 312 | 313 | #### drawShape 314 | 315 | Draw one shape 316 | 317 | ##### Parameters 318 | 319 | * `modele` **type** 320 | 321 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 322 | 323 | #### align 324 | 325 | Aligne les éléments sélectionnés 326 | 327 | ##### Parameters 328 | 329 | * `type` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** (top,middle,bottom,left,center,right) 330 | 331 | Returns **[undefined](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined)** 332 | 333 | #### load 334 | 335 | Load a document from a file, an url, a xml string or a xml node 336 | 337 | ##### Parameters 338 | 339 | * `arg` 340 | 341 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 342 | 343 | #### loadString 344 | 345 | Load a document from a svg string 346 | 347 | ##### Parameters 348 | 349 | * `str` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 350 | 351 | Returns **JSYG.FullEditor** 352 | 353 | #### readFile 354 | 355 | Read a File instance 356 | 357 | ##### Parameters 358 | 359 | * `file` **File** 360 | * `readAs` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** optional, "DataURL" or "Text" ("Text" by default) 361 | 362 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 363 | 364 | #### loadFile 365 | 366 | Load a document from a File instance 367 | 368 | ##### Parameters 369 | 370 | * `file` **File** 371 | 372 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 373 | 374 | #### loadURL 375 | 376 | Load a document from an url 377 | 378 | ##### Parameters 379 | 380 | * `url` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 381 | 382 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 383 | 384 | #### loadXML 385 | 386 | Load a document from a xml node 387 | 388 | ##### Parameters 389 | 390 | * `svg` **DOMElement** 391 | 392 | Returns **JSYG.FullEditor** 393 | 394 | #### newDocument 395 | 396 | Create a new document 397 | 398 | ##### Parameters 399 | 400 | * `width` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** 401 | * `height` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** 402 | 403 | Returns **JSYG.FullEditor** 404 | 405 | #### toCanvas 406 | 407 | Convert document to a canvas element 408 | 409 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 410 | 411 | #### toSVGString 412 | 413 | Convert document to a SVG string (keep links) 414 | 415 | ##### Parameters 416 | 417 | * `opt` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** options (for the moment only "standalone" as boolean, to converts links to dataURLs) 418 | 419 | ##### Examples 420 | 421 | ```javascript 422 | fullEditor.toSVGString({standalone:true}) 423 | ``` 424 | 425 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 426 | 427 | #### toSVGDataURL 428 | 429 | Convert document to a SVG data url 430 | 431 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 432 | 433 | #### toPNGDataURL 434 | 435 | Convert document to a PNG data url 436 | 437 | ##### Parameters 438 | 439 | * `format` 440 | 441 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 442 | 443 | #### toDataURL 444 | 445 | Convert document to data URL 446 | 447 | ##### Parameters 448 | 449 | * `format` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** "svg" or "png" 450 | 451 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 452 | 453 | #### print 454 | 455 | Print document 456 | 457 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 458 | 459 | #### downloadPNG 460 | 461 | Download document as PNG 462 | 463 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 464 | 465 | #### downloadSVG 466 | 467 | Download document as SVG 468 | 469 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 470 | 471 | #### download 472 | 473 | Download document 474 | 475 | ##### Parameters 476 | 477 | * `format` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** "png" or "svg" 478 | 479 | Returns **JSYG.FullEditor** 480 | 481 | #### remove 482 | 483 | Remove selection 484 | 485 | Returns **JSYG.FullEditor** 486 | 487 | #### group 488 | 489 | Group selected elements 490 | 491 | Returns **JSYG.FullEditor** 492 | 493 | #### ungroup 494 | 495 | Ungroup selection 496 | 497 | Returns **JSYG.FullEditor** 498 | 499 | #### center 500 | 501 | Center selected elements 502 | 503 | ##### Parameters 504 | 505 | * `orientation` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** "vertical" or "horizontal" 506 | 507 | Returns **JSYG.FullEditor** 508 | 509 | #### centerVertically 510 | 511 | Center selected elements vertically 512 | 513 | Returns **JSYG.FullEditor** 514 | 515 | #### centerHorizontally 516 | 517 | Center selected elements horizontally 518 | 519 | Returns **JSYG.FullEditor** 520 | 521 | #### registerPlugin 522 | 523 | Register a plugin 524 | 525 | ##### Parameters 526 | 527 | * `plugin` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** 528 | 529 | Returns **JSYG.FullEditor** 530 | 531 | #### defineProperty 532 | 533 | ##### Properties 534 | 535 | * `editText` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if text elements can be edited or not 536 | 537 | #### defineProperty 538 | 539 | ##### Properties 540 | 541 | * `editPosition` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if elements position can be edited or not 542 | 543 | #### defineProperty 544 | 545 | ##### Properties 546 | 547 | * `editSize` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if elements size can be edited or not 548 | 549 | #### defineProperty 550 | 551 | ##### Properties 552 | 553 | * `editRotation` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if elements rotation can be edited or not 554 | 555 | #### defineProperty 556 | 557 | ##### Properties 558 | 559 | * `editPathMainPoints` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if main points of paths can be edited or not 560 | 561 | #### defineProperty 562 | 563 | ##### Properties 564 | 565 | * `editPathCtrlPoints` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if control points of paths can be edited or not 566 | 567 | #### defineProperty 568 | 569 | ##### Properties 570 | 571 | * `canvasResizable` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if the editor can be resized or not 572 | 573 | #### defineProperty 574 | 575 | ##### Properties 576 | 577 | * `keepShapesRatio` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if ratio must be kept when resizing 578 | 579 | #### defineProperty 580 | 581 | ##### Properties 582 | 583 | * `drawingPathMethod` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** "freehand" or "point2point". Set method of drawing paths 584 | 585 | #### defineProperty 586 | 587 | ##### Properties 588 | 589 | * `autoSmoothPaths` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if paths must be smoothed automatically when drawing 590 | 591 | #### defineProperty 592 | 593 | ##### Properties 594 | 595 | * `useTransformAttr` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set if transform attribute must be affected when editing size and position, instead 596 | of position and size attributes 597 | 598 | #### defineProperty 599 | 600 | ##### Properties 601 | 602 | * `currentLayer` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** set current layer of edition 603 | 604 | #### defineProperty 605 | 606 | ##### Properties 607 | 608 | * `shapeDrawerModel` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** dom node to clone when starting drawing 609 | -------------------------------------------------------------------------------- /examples/firefox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Firefox Logo 6 | Firefox logo in SVG, authored by Jon Hicks, made in Adobe Illustrator (file format cleaned up and slightly modified by Doug Schepers) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /examples/linux.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSYG Full Editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 42 | 43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | 52 |
53 |
54 | 61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 | 77 | 78 |
79 |
80 | 81 | 82 |
83 |
84 |
85 | 86 | 87 | 88 |
89 |
90 |
91 |
92 |
93 | 100 |
101 |
102 |
103 | 104 | 108 | 109 | 113 | 114 | 118 | 119 | 123 |

124 | 128 | 129 | 133 |
134 |
135 | You can use usual key shortcuts (Ctrl+C, Ctrl+X, Ctrl+V, Del, Ctrl+Z, Ctrl+Y) 136 |
137 |
138 |
139 |
140 |
141 | 148 |
149 |
150 |
151 |

Use Ctrl+mousewheel to zoom in and out.

152 | 156 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
167 |
168 |
169 |
170 |
171 | 178 |
179 |
180 |
181 | 192 |
193 |
194 |
195 |
196 | 197 |
198 | 205 |
206 |
207 |
208 | 209 |

Use Ctrl+click to select multiple elements (or draw a selection with mouse).

210 | 211 | 212 | 213 | 214 | 215 |
216 |
217 | Align selected elements
218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 |
226 |
227 | 228 | 229 | 230 | 231 | 232 | 233 |
234 |
235 |
236 |
237 | 238 |
239 | 246 |
247 |
248 |
249 | 250 | 253 |
254 | 257 |
258 | 261 |
262 | 263 | 266 |
267 | 268 | 271 | 272 | 275 | 276 |
277 | 278 | 281 | 282 |
283 | 284 | 287 | 288 |
289 | 290 | 293 | 294 |
295 | 298 | 299 |
300 |
301 |
302 |
303 |
304 | 305 | 325 | 326 |
327 |
328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsyg-fulleditor", 3 | "version": "1.1.0", 4 | "description": "complete and very simple API to create svg editor", 5 | "main": "JSYG.FullEditor.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://contact@ybochatay.fr@github.com/YannickBochatay/JSYG.FullEditor.git" 12 | }, 13 | "keywords": [ 14 | "svg", 15 | "jsyg", 16 | "jquery", 17 | "javascript", 18 | "library" 19 | ], 20 | "author": "Yannick Bochatay", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/YannickBochatay/JSYG.FullEditor/issues" 24 | }, 25 | "homepage": "https://github.com/YannickBochatay/JSYG.FullEditor#readme", 26 | "scripts": { 27 | "grunt": "grunt", 28 | "doc": "documentation readme JSYG.FullEditor.js --section=API" 29 | }, 30 | "devDependencies": { 31 | "bootstrap": "^5.3.3", 32 | "documentation": "^14.0.3", 33 | "grunt": "^1.0.2", 34 | "grunt-contrib-concat": "^1.0.1" 35 | }, 36 | "dependencies": { 37 | "jquery.hotkeys": "^0.1.0", 38 | "jsyg": "^1.1.0", 39 | "jsyg-alignment": "^1.0.0", 40 | "jsyg-cropandresize": "^1.0.0", 41 | "jsyg-editor": "^1.1.0", 42 | "jsyg-pathdrawer": "^1.0.0", 43 | "jsyg-polylinedrawer": "^1.0.0", 44 | "jsyg-shapedrawer": "^1.0.0", 45 | "jsyg-texteditor": "^1.0.0", 46 | "jsyg-undoredo": "^1.0.0", 47 | "jsyg-zoomandpan": "^1.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | window.svgEditor = new JSYG.FullEditor('svg'); 4 | 5 | svgEditor.editableShapes = "> *"; 6 | 7 | svgEditor.enable(); 8 | 9 | 10 | ["left","center","right","top","middle","bottom"].forEach(function(type) { 11 | 12 | $('#align'+JSYG.ucfirst(type)).on("click",function() { 13 | svgEditor.align(type); 14 | }) 15 | }); 16 | 17 | ["Front","Back","ToFront","ToBack"].forEach(function(type) { 18 | 19 | $('#move'+type).on("click",function() { 20 | svgEditor["moveTarget"+type](); 21 | }); 22 | }); 23 | 24 | $("#insertText").on("click",function() { 25 | var text = new JSYG("").text("Bonjour le monde"); 26 | svgEditor.enableInsertElement(text); 27 | new JSYG(this).trigger("blur"); 28 | }); 29 | 30 | $("#newDocument").on("click",function() { 31 | svgEditor.newDocument( $('#width').val(), $('#height').val() ); 32 | }); 33 | 34 | $("#openDocument").on("click",function() { 35 | 36 | svgEditor.chooseFile().then(svgEditor.loadFile).catch(alert); 37 | }); 38 | 39 | $("#openImage").on("click",function() { 40 | 41 | svgEditor.chooseFile().then(svgEditor.loadImageAsDoc).catch(alert); 42 | }); 43 | 44 | $("#insertImage").on("click",function() { 45 | 46 | svgEditor.chooseFile().then(svgEditor.insertImageFile).catch(alert); 47 | }); 48 | 49 | $("#downloadSVG").on("click",function() { 50 | 51 | svgEditor.download("svg"); 52 | }); 53 | 54 | $("#downloadPNG").on("click",function() { 55 | 56 | svgEditor.download("png"); 57 | }); 58 | 59 | $('#openExample').on("click",function() { 60 | $('#exampleChoice').modal(); 61 | }); 62 | 63 | $('#confirmExample').on("click",function() { 64 | $('#exampleChoice').modal("hide"); 65 | svgEditor.loadURL('examples/' + $('#examples').val() + '.svg'); 66 | }); 67 | 68 | svgEditor.on("load",function() { 69 | var dim = svgEditor.dimDocument(); 70 | $('#width').val(dim.width); 71 | $('#height').val(dim.height); 72 | }); 73 | 74 | $('#width').on("change",function() { 75 | svgEditor.dimDocument({width:this.value}); 76 | }); 77 | 78 | $('#height').on("change",function() { 79 | svgEditor.dimDocument({height:this.value}); 80 | }); 81 | 82 | 83 | $('.collapse').collapse({parent:"#accordion"}); 84 | 85 | $('#viewPanel').on("hide.bs.collapse",function() { 86 | svgEditor.disableMousePan(); 87 | $('#mousePan').removeClass("active"); 88 | }); 89 | 90 | $('#mousePan').on("click",function() { 91 | svgEditor.enableMousePan(); 92 | $(this).addClass("active"); 93 | }); 94 | 95 | $('#drawShapes').on({ 96 | "show.bs.collapse":function () { 97 | $('#shape').trigger("change"); 98 | }, 99 | "hide.bs.collapse":function() { 100 | svgEditor.disableShapeDrawer(); 101 | svgEditor.disableInsertElement(); 102 | svgEditor.enableSelection(); 103 | } 104 | }); 105 | 106 | $('#shape').on("change",function() { 107 | 108 | var type = this.value; 109 | 110 | if (type.indexOf("path")!=-1) { 111 | svgEditor.drawingPathMethod = (type == "path") ? "point2point" : "freehand"; 112 | type = "path"; 113 | } 114 | 115 | var shape = new JSYG("<"+type+">").addClass("perso"); 116 | 117 | if (type == "text") svgEditor.enableInsertElement(shape); 118 | else svgEditor.enableShapeDrawer(shape); 119 | }); 120 | 121 | $('#marqueeZoom').on("click",function() { 122 | svgEditor.marqueeZoom(); 123 | }); 124 | 125 | $('#fitToCanvas').on("click",function() { 126 | svgEditor.zoomTo('canvas'); 127 | }); 128 | 129 | $('#fitToDoc').on("click",function() { 130 | svgEditor.fitToDoc(); 131 | }); 132 | 133 | $('#realSize').on("click",function() { 134 | svgEditor.zoomTo(100); 135 | }); 136 | 137 | $('#zoomIn').on("click",function() { 138 | svgEditor.zoom(+10); 139 | }); 140 | 141 | $('#zoomOut').on("click",function() { 142 | svgEditor.zoom(-10); 143 | }); 144 | 145 | ["remove","copy","cut","paste","undo","redo","group","ungroup"].forEach(function(action) { 146 | 147 | $('#'+action).on("click",function() { 148 | svgEditor[action](); 149 | }); 150 | }); 151 | 152 | ["canvasResizable","editPathMainPoints","editPathCtrlPoints","keepShapesRatio","autoSmoothPaths","useTransformAttr","editPosition","editSize","editRotation","editText"].forEach(function(property) { 153 | 154 | $('#'+property).on("change",function() { 155 | svgEditor[property] = this.checked; 156 | new JSYG(this).blur(); 157 | }).trigger("change"); 158 | }); 159 | 160 | $('#print').on("click",function() { svgEditor.print(); }); 161 | 162 | svgEditor.registerKeyShortCut({ 163 | "ctrl+c": svgEditor.copy, 164 | "ctrl+x": svgEditor.cut, 165 | "ctrl+v": svgEditor.paste, 166 | "ctrl+z": svgEditor.undo, 167 | "ctrl+y": svgEditor.redo, 168 | "ctrl+a":svgEditor.selectAll, 169 | "del": svgEditor.remove, 170 | "up" : function(e) { e.preventDefault(); svgEditor.dim("y","-=1"); }, 171 | "down" : function(e) { e.preventDefault(); svgEditor.dim("y","+=1"); }, 172 | "left" : function(e) { e.preventDefault(); svgEditor.dim("x","-=1"); }, 173 | "right" : function(e) { e.preventDefault(); svgEditor.dim("x","+=1"); } 174 | }); 175 | 176 | svgEditor.newDocument(500,500); 177 | 178 | svgEditor.enableDropFiles(); 179 | 180 | svgEditor.enableMouseWheelZoom(); 181 | }); 182 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .jsyg-doc-frame { 2 | fill: #FFF; 3 | stroke: #000; 4 | stroke-width: 0.2; 5 | } 6 | .jsyg-doc-shadow { 7 | fill: #808080; 8 | stroke: none; 9 | } 10 | --------------------------------------------------------------------------------