source: other-projects/nz-flag-design/trunk/design-2d/Original editor.method.ac/method-draw/src/draw.js@ 29468

Last change on this file since 29468 was 29468, checked in by sjs49, 9 years ago

Initial commit for editor.method.ac for flag design

  • Property svn:executable set to *
File size: 15.8 KB
Line 
1/**
2 * Package: svgedit.draw
3 *
4 * Licensed under the Apache License, Version 2
5 *
6 * Copyright(c) 2011 Jeff Schiller
7 */
8
9// Dependencies:
10// 1) jQuery
11// 2) browser.js
12// 3) svgutils.js
13
14var svgedit = svgedit || {};
15
16(function() {
17
18if (!svgedit.draw) {
19 svgedit.draw = {};
20}
21
22var svg_ns = "http://www.w3.org/2000/svg";
23var se_ns = "http://svg-edit.googlecode.com";
24var xmlns_ns = "http://www.w3.org/2000/xmlns/";
25
26var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
27var visElems_arr = visElems.split(',');
28
29var RandomizeModes = {
30 LET_DOCUMENT_DECIDE: 0,
31 ALWAYS_RANDOMIZE: 1,
32 NEVER_RANDOMIZE: 2
33};
34var randomize_ids = RandomizeModes.LET_DOCUMENT_DECIDE;
35
36/**
37 * This class encapsulates the concept of a layer in the drawing
38 * @param name {String} Layer name
39 * @param child {SVGGElement} Layer SVG group.
40 */
41svgedit.draw.Layer = function(name, group) {
42 this.name_ = name;
43 this.group_ = group;
44};
45
46svgedit.draw.Layer.prototype.getName = function() {
47 return this.name_;
48};
49
50svgedit.draw.Layer.prototype.getGroup = function() {
51 return this.group_;
52};
53
54
55// Called to ensure that drawings will or will not have randomized ids.
56// The current_drawing will have its nonce set if it doesn't already.
57//
58// Params:
59// enableRandomization - flag indicating if documents should have randomized ids
60svgedit.draw.randomizeIds = function(enableRandomization, current_drawing) {
61 randomize_ids = enableRandomization == false ?
62 RandomizeModes.NEVER_RANDOMIZE :
63 RandomizeModes.ALWAYS_RANDOMIZE;
64
65 if (randomize_ids == RandomizeModes.ALWAYS_RANDOMIZE && !current_drawing.getNonce()) {
66 current_drawing.setNonce(Math.floor(Math.random() * 100001));
67 } else if (randomize_ids == RandomizeModes.NEVER_RANDOMIZE && current_drawing.getNonce()) {
68 current_drawing.clearNonce();
69 }
70};
71
72/**
73 * This class encapsulates the concept of a SVG-edit drawing
74 *
75 * @param svgElem {SVGSVGElement} The SVG DOM Element that this JS object
76 * encapsulates. If the svgElem has a se:nonce attribute on it, then
77 * IDs will use the nonce as they are generated.
78 * @param opt_idPrefix {String} The ID prefix to use. Defaults to "svg_"
79 * if not specified.
80 */
81svgedit.draw.Drawing = function(svgElem, opt_idPrefix) {
82 if (!svgElem || !svgElem.tagName || !svgElem.namespaceURI ||
83 svgElem.tagName != 'svg' || svgElem.namespaceURI != svg_ns) {
84 throw "Error: svgedit.draw.Drawing instance initialized without a <svg> element";
85 }
86
87 /**
88 * The SVG DOM Element that represents this drawing.
89 * @type {SVGSVGElement}
90 */
91 this.svgElem_ = svgElem;
92
93 /**
94 * The latest object number used in this drawing.
95 * @type {number}
96 */
97 this.obj_num = 0;
98
99 /**
100 * The prefix to prepend to each element id in the drawing.
101 * @type {String}
102 */
103 this.idPrefix = opt_idPrefix || "svg_";
104
105 /**
106 * An array of released element ids to immediately reuse.
107 * @type {Array.<number>}
108 */
109 this.releasedNums = [];
110
111 /**
112 * The z-ordered array of tuples containing layer names and <g> elements.
113 * The first layer is the one at the bottom of the rendering.
114 * TODO: Turn this into an Array.<Layer>
115 * @type {Array.<Array.<String, SVGGElement>>}
116 */
117 this.all_layers = [];
118
119 /**
120 * The current layer being used.
121 * TODO: Make this a {Layer}.
122 * @type {SVGGElement}
123 */
124 this.current_layer = null;
125
126 /**
127 * The nonce to use to uniquely identify elements across drawings.
128 * @type {!String}
129 */
130 this.nonce_ = "";
131 var n = this.svgElem_.getAttributeNS(se_ns, 'nonce');
132 // If already set in the DOM, use the nonce throughout the document
133 // else, if randomizeIds(true) has been called, create and set the nonce.
134 if (!!n && randomize_ids != RandomizeModes.NEVER_RANDOMIZE) {
135 this.nonce_ = n;
136 } else if (randomize_ids == RandomizeModes.ALWAYS_RANDOMIZE) {
137 this.setNonce(Math.floor(Math.random() * 100001));
138 }
139};
140
141svgedit.draw.Drawing.prototype.getElem_ = function(id) {
142 if(this.svgElem_.querySelector) {
143 // querySelector lookup
144 return this.svgElem_.querySelector('#'+id);
145 } else {
146 // jQuery lookup: twice as slow as xpath in FF
147 return $(this.svgElem_).find('[id=' + id + ']')[0];
148 }
149};
150
151svgedit.draw.Drawing.prototype.getSvgElem = function() {
152 return this.svgElem_;
153};
154
155svgedit.draw.Drawing.prototype.getNonce = function() {
156 return this.nonce_;
157};
158
159svgedit.draw.Drawing.prototype.setNonce = function(n) {
160 this.svgElem_.setAttributeNS(xmlns_ns, 'xmlns:se', se_ns);
161 this.svgElem_.setAttributeNS(se_ns, 'se:nonce', n);
162 this.nonce_ = n;
163};
164
165svgedit.draw.Drawing.prototype.clearNonce = function() {
166 // We deliberately leave any se:nonce attributes alone,
167 // we just don't use it to randomize ids.
168 this.nonce_ = "";
169};
170
171/**
172 * Returns the latest object id as a string.
173 * @return {String} The latest object Id.
174 */
175svgedit.draw.Drawing.prototype.getId = function() {
176 return this.nonce_ ?
177 this.idPrefix + this.nonce_ +'_' + this.obj_num :
178 this.idPrefix + this.obj_num;
179};
180
181/**
182 * Returns the next object Id as a string.
183 * @return {String} The next object Id to use.
184 */
185svgedit.draw.Drawing.prototype.getNextId = function() {
186 var oldObjNum = this.obj_num;
187 var restoreOldObjNum = false;
188
189 // If there are any released numbers in the release stack,
190 // use the last one instead of the next obj_num.
191 // We need to temporarily use obj_num as that is what getId() depends on.
192 if (this.releasedNums.length > 0) {
193 this.obj_num = this.releasedNums.pop();
194 restoreOldObjNum = true;
195 } else {
196 // If we are not using a released id, then increment the obj_num.
197 this.obj_num++;
198 }
199
200 // Ensure the ID does not exist.
201 var id = this.getId();
202 while (this.getElem_(id)) {
203 if (restoreOldObjNum) {
204 this.obj_num = oldObjNum;
205 restoreOldObjNum = false;
206 }
207 this.obj_num++;
208 id = this.getId();
209 }
210 // Restore the old object number if required.
211 if (restoreOldObjNum) {
212 this.obj_num = oldObjNum;
213 }
214 return id;
215};
216
217// Function: svgedit.draw.Drawing.releaseId
218// Releases the object Id, letting it be used as the next id in getNextId().
219// This method DOES NOT remove any elements from the DOM, it is expected
220// that client code will do this.
221//
222// Parameters:
223// id - The id to release.
224//
225// Returns:
226// True if the id was valid to be released, false otherwise.
227svgedit.draw.Drawing.prototype.releaseId = function(id) {
228 // confirm if this is a valid id for this Document, else return false
229 var front = this.idPrefix + (this.nonce_ ? this.nonce_ +'_' : '');
230 if (typeof id != typeof '' || id.indexOf(front) != 0) {
231 return false;
232 }
233 // extract the obj_num of this id
234 var num = parseInt(id.substr(front.length));
235
236 // if we didn't get a positive number or we already released this number
237 // then return false.
238 if (typeof num != typeof 1 || num <= 0 || this.releasedNums.indexOf(num) != -1) {
239 return false;
240 }
241
242 // push the released number into the released queue
243 this.releasedNums.push(num);
244
245 return true;
246};
247
248// Function: svgedit.draw.Drawing.getNumLayers
249// Returns the number of layers in the current drawing.
250//
251// Returns:
252// The number of layers in the current drawing.
253svgedit.draw.Drawing.prototype.getNumLayers = function() {
254 return this.all_layers.length;
255};
256
257// Function: svgedit.draw.Drawing.hasLayer
258// Check if layer with given name already exists
259svgedit.draw.Drawing.prototype.hasLayer = function(name) {
260 for(var i = 0; i < this.getNumLayers(); i++) {
261 if(this.all_layers[i][0] == name) return true;
262 }
263 return false;
264};
265
266
267// Function: svgedit.draw.Drawing.getLayerName
268// Returns the name of the ith layer. If the index is out of range, an empty string is returned.
269//
270// Parameters:
271// i - the zero-based index of the layer you are querying.
272//
273// Returns:
274// The name of the ith layer
275svgedit.draw.Drawing.prototype.getLayerName = function(i) {
276 if (i >= 0 && i < this.getNumLayers()) {
277 return this.all_layers[i][0];
278 }
279 return "";
280};
281
282// Function: svgedit.draw.Drawing.getCurrentLayer
283// Returns:
284// The SVGGElement representing the current layer.
285svgedit.draw.Drawing.prototype.getCurrentLayer = function() {
286 return this.current_layer;
287};
288
289// Function: getCurrentLayerName
290// Returns the name of the currently selected layer. If an error occurs, an empty string
291// is returned.
292//
293// Returns:
294// The name of the currently active layer.
295svgedit.draw.Drawing.prototype.getCurrentLayerName = function() {
296 for (var i = 0; i < this.getNumLayers(); ++i) {
297 if (this.all_layers[i][1] == this.current_layer) {
298 return this.getLayerName(i);
299 }
300 }
301 return "";
302};
303
304// Function: setCurrentLayer
305// Sets the current layer. If the name is not a valid layer name, then this function returns
306// false. Otherwise it returns true. This is not an undo-able action.
307//
308// Parameters:
309// name - the name of the layer you want to switch to.
310//
311// Returns:
312// true if the current layer was switched, otherwise false
313svgedit.draw.Drawing.prototype.setCurrentLayer = function(name) {
314 for (var i = 0; i < this.getNumLayers(); ++i) {
315 if (name == this.getLayerName(i)) {
316 if (this.current_layer != this.all_layers[i][1]) {
317 this.current_layer.setAttribute("style", "pointer-events:none");
318 this.current_layer = this.all_layers[i][1];
319 this.current_layer.setAttribute("style", "pointer-events:all");
320 }
321 return true;
322 }
323 }
324 return false;
325};
326
327
328// Function: svgedit.draw.Drawing.deleteCurrentLayer
329// Deletes the current layer from the drawing and then clears the selection. This function
330// then calls the 'changed' handler. This is an undoable action.
331// Returns:
332// The SVGGElement of the layer removed or null.
333svgedit.draw.Drawing.prototype.deleteCurrentLayer = function() {
334 if (this.current_layer && this.getNumLayers() > 1) {
335 // actually delete from the DOM and return it
336 var parent = this.current_layer.parentNode;
337 var nextSibling = this.current_layer.nextSibling;
338 var oldLayerGroup = parent.removeChild(this.current_layer);
339 this.identifyLayers();
340 return oldLayerGroup;
341 }
342 return null;
343};
344
345// Function: svgedit.draw.Drawing.identifyLayers
346// Updates layer system and sets the current layer to the
347// top-most layer (last <g> child of this drawing).
348svgedit.draw.Drawing.prototype.identifyLayers = function() {
349 this.all_layers = [];
350 var numchildren = this.svgElem_.childNodes.length;
351 // loop through all children of SVG element
352 var orphans = [], layernames = [];
353 var a_layer = null;
354 var childgroups = false;
355 for (var i = 0; i < numchildren; ++i) {
356 var child = this.svgElem_.childNodes.item(i);
357 // for each g, find its layer name
358 if (child && child.nodeType == 1) {
359 if (child.tagName == "g") {
360 childgroups = true;
361 var name = $("title",child).text();
362
363 // Hack for Opera 10.60
364 if(!name && svgedit.browser.isOpera() && child.querySelectorAll) {
365 name = $(child.querySelectorAll('title')).text();
366 }
367
368 // store layer and name in global variable
369 if (name) {
370 layernames.push(name);
371 this.all_layers.push( [name,child] );
372 a_layer = child;
373 svgedit.utilities.walkTree(child, function(e){e.setAttribute("style", "pointer-events:inherit");});
374 a_layer.setAttribute("style", "pointer-events:none");
375 }
376 // if group did not have a name, it is an orphan
377 else {
378 orphans.push(child);
379 }
380 }
381 // if child has is "visible" (i.e. not a <title> or <defs> element), then it is an orphan
382 else if(~visElems_arr.indexOf(child.nodeName)) {
383 var bb = svgedit.utilities.getBBox(child);
384 orphans.push(child);
385 }
386 }
387 }
388
389 // create a new layer and add all the orphans to it
390 var svgdoc = this.svgElem_.ownerDocument;
391 if (orphans.length > 0 || !childgroups) {
392 var i = 1;
393 // TODO(codedread): What about internationalization of "Layer"?
394 while (layernames.indexOf(("Layer " + i)) >= 0) { i++; }
395 var newname = "Layer " + i;
396 a_layer = svgdoc.createElementNS(svg_ns, "g");
397 var layer_title = svgdoc.createElementNS(svg_ns, "title");
398 layer_title.textContent = newname;
399 a_layer.appendChild(layer_title);
400 for (var j = 0; j < orphans.length; ++j) {
401 a_layer.appendChild(orphans[j]);
402 }
403 this.svgElem_.appendChild(a_layer);
404 this.all_layers.push( [newname, a_layer] );
405 }
406 svgedit.utilities.walkTree(a_layer, function(e){e.setAttribute("style","pointer-events:inherit");});
407 if (a_layer.getAttribute("data-locked") === "true") {
408 this.current_layer = this.all_layers.slice(-2)[0][1]
409 }
410 else {
411 this.current_layer = a_layer
412 }
413 this.current_layer.setAttribute("style","pointer-events:all");
414};
415
416// Function: svgedit.draw.Drawing.createLayer
417// Creates a new top-level layer in the drawing with the given name and
418// sets the current layer to it.
419//
420// Parameters:
421// name - The given name
422//
423// Returns:
424// The SVGGElement of the new layer, which is also the current layer
425// of this drawing.
426svgedit.draw.Drawing.prototype.createLayer = function(name) {
427 var svgdoc = this.svgElem_.ownerDocument;
428 var new_layer = svgdoc.createElementNS(svg_ns, "g");
429 var layer_title = svgdoc.createElementNS(svg_ns, "title");
430 layer_title.textContent = name;
431 new_layer.appendChild(layer_title);
432 this.svgElem_.appendChild(new_layer);
433 this.identifyLayers();
434 return new_layer;
435};
436
437// Function: svgedit.draw.Drawing.getLayerVisibility
438// Returns whether the layer is visible. If the layer name is not valid, then this function
439// returns false.
440//
441// Parameters:
442// layername - the name of the layer which you want to query.
443//
444// Returns:
445// The visibility state of the layer, or false if the layer name was invalid.
446svgedit.draw.Drawing.prototype.getLayerVisibility = function(layername) {
447 // find the layer
448 var layer = null;
449 for (var i = 0; i < this.getNumLayers(); ++i) {
450 if (this.getLayerName(i) == layername) {
451 layer = this.all_layers[i][1];
452 break;
453 }
454 }
455 if (!layer) return false;
456 return (layer.getAttribute('display') != 'none');
457};
458
459// Function: svgedit.draw.Drawing.setLayerVisibility
460// Sets the visibility of the layer. If the layer name is not valid, this function return
461// false, otherwise it returns true. This is an undo-able action.
462//
463// Parameters:
464// layername - the name of the layer to change the visibility
465// bVisible - true/false, whether the layer should be visible
466//
467// Returns:
468// The SVGGElement representing the layer if the layername was valid, otherwise null.
469svgedit.draw.Drawing.prototype.setLayerVisibility = function(layername, bVisible) {
470 if (typeof bVisible != typeof true) {
471 return null;
472 }
473 // find the layer
474 var layer = null;
475 for (var i = 0; i < this.getNumLayers(); ++i) {
476 if (this.getLayerName(i) == layername) {
477 layer = this.all_layers[i][1];
478 break;
479 }
480 }
481 if (!layer) return null;
482
483 var oldDisplay = layer.getAttribute("display");
484 if (!oldDisplay) oldDisplay = "inline";
485 layer.setAttribute("display", bVisible ? "inline" : "none");
486 return layer;
487};
488
489
490// Function: svgedit.draw.Drawing.getLayerOpacity
491// Returns the opacity of the given layer. If the input name is not a layer, null is returned.
492//
493// Parameters:
494// layername - name of the layer on which to get the opacity
495//
496// Returns:
497// The opacity value of the given layer. This will be a value between 0.0 and 1.0, or null
498// if layername is not a valid layer
499svgedit.draw.Drawing.prototype.getLayerOpacity = function(layername) {
500 for (var i = 0; i < this.getNumLayers(); ++i) {
501 if (this.getLayerName(i) == layername) {
502 var g = this.all_layers[i][1];
503 var opacity = g.getAttribute('opacity');
504 if (!opacity) {
505 opacity = '1.0';
506 }
507 return parseFloat(opacity);
508 }
509 }
510 return null;
511};
512
513// Function: svgedit.draw.Drawing.setLayerOpacity
514// Sets the opacity of the given layer. If the input name is not a layer, nothing happens.
515// If opacity is not a value between 0.0 and 1.0, then nothing happens.
516//
517// Parameters:
518// layername - name of the layer on which to set the opacity
519// opacity - a float value in the range 0.0-1.0
520svgedit.draw.Drawing.prototype.setLayerOpacity = function(layername, opacity) {
521 if (typeof opacity != typeof 1.0 || opacity < 0.0 || opacity > 1.0) {
522 return;
523 }
524 for (var i = 0; i < this.getNumLayers(); ++i) {
525 if (this.getLayerName(i) == layername) {
526 var g = this.all_layers[i][1];
527 g.setAttribute("opacity", opacity);
528 break;
529 }
530 }
531};
532
533})();
Note: See TracBrowser for help on using the repository browser.