Translation update done using Pootle.
[phpmyadmin/ammaryasirr.git] / js / jquery / jquery.svg.js
blob4f3dd56de63b9fd4f6d4ff5838937035ca38e83b
1 /* http://keith-wood.name/svg.html
2    SVG for jQuery v1.4.3.
3    Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
4    Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
5    MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
6    Please attribute the author if you use it. */
8 (function($) { // Hide scope, no $ conflict
10 /* SVG manager.
11    Use the singleton instance of this class, $.svg, 
12    to interact with the SVG functionality. */
13 function SVGManager() {
14         this._settings = []; // Settings to be remembered per SVG object
15         this._extensions = []; // List of SVG extensions added to SVGWrapper
16                 // for each entry [0] is extension name, [1] is extension class (function)
17                 // the function takes one parameter - the SVGWrapper instance
18         this.regional = []; // Localisations, indexed by language, '' for default (English)
19         this.regional[''] = {errorLoadingText: 'Error loading',
20                 notSupportedText: 'This browser does not support SVG'};
21         this.local = this.regional['']; // Current localisation
22         this._uuid = new Date().getTime();
23         this._renesis = detectActiveX('RenesisX.RenesisCtrl');
26 /* Determine whether a given ActiveX control is available.
27    @param  classId  (string) the ID for the ActiveX control
28    @return  (boolean) true if found, false if not */
29 function detectActiveX(classId) {
30         try {
31                 return !!(window.ActiveXObject && new ActiveXObject(classId));
32         }
33         catch (e) {
34                 return false;
35         }
38 var PROP_NAME = 'svgwrapper';
40 $.extend(SVGManager.prototype, {
41         /* Class name added to elements to indicate already configured with SVG. */
42         markerClassName: 'hasSVG',
44         /* SVG namespace. */
45         svgNS: 'http://www.w3.org/2000/svg',
46         /* XLink namespace. */
47         xlinkNS: 'http://www.w3.org/1999/xlink',
49         /* SVG wrapper class. */
50         _wrapperClass: SVGWrapper,
52         /* Camel-case versions of attribute names containing dashes or are reserved words. */
53         _attrNames: {class_: 'class', in_: 'in',
54                 alignmentBaseline: 'alignment-baseline', baselineShift: 'baseline-shift',
55                 clipPath: 'clip-path', clipRule: 'clip-rule',
56                 colorInterpolation: 'color-interpolation',
57                 colorInterpolationFilters: 'color-interpolation-filters',
58                 colorRendering: 'color-rendering', dominantBaseline: 'dominant-baseline',
59                 enableBackground: 'enable-background', fillOpacity: 'fill-opacity',
60                 fillRule: 'fill-rule', floodColor: 'flood-color',
61                 floodOpacity: 'flood-opacity', fontFamily: 'font-family',
62                 fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust',
63                 fontStretch: 'font-stretch', fontStyle: 'font-style',
64                 fontVariant: 'font-variant', fontWeight: 'font-weight',
65                 glyphOrientationHorizontal: 'glyph-orientation-horizontal',
66                 glyphOrientationVertical: 'glyph-orientation-vertical',
67                 horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x',
68                 imageRendering: 'image-rendering', letterSpacing: 'letter-spacing',
69                 lightingColor: 'lighting-color', markerEnd: 'marker-end',
70                 markerMid: 'marker-mid', markerStart: 'marker-start',
71                 stopColor: 'stop-color', stopOpacity: 'stop-opacity',
72                 strikethroughPosition: 'strikethrough-position',
73                 strikethroughThickness: 'strikethrough-thickness',
74                 strokeDashArray: 'stroke-dasharray', strokeDashOffset: 'stroke-dashoffset',
75                 strokeLineCap: 'stroke-linecap', strokeLineJoin: 'stroke-linejoin',
76                 strokeMiterLimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity',
77                 strokeWidth: 'stroke-width', textAnchor: 'text-anchor',
78                 textDecoration: 'text-decoration', textRendering: 'text-rendering',
79                 underlinePosition: 'underline-position', underlineThickness: 'underline-thickness',
80                 vertAdvY: 'vert-adv-y', vertOriginY: 'vert-origin-y',
81                 wordSpacing: 'word-spacing', writingMode: 'writing-mode'},
83         /* Add the SVG object to its container. */
84         _attachSVG: function(container, settings) {
85                 var svg = (container.namespaceURI == this.svgNS ? container : null);
86                 var container = (svg ? null : container);
87                 if ($(container || svg).hasClass(this.markerClassName)) {
88                         return;
89                 }
90                 if (typeof settings == 'string') {
91                         settings = {loadURL: settings};
92                 }
93                 else if (typeof settings == 'function') {
94                         settings = {onLoad: settings};
95                 }
96                 $(container || svg).addClass(this.markerClassName);
97                 try {
98                         if (!svg) {
99                                 svg = document.createElementNS(this.svgNS, 'svg');
100                                 svg.setAttribute('version', '1.1');
101                                 svg.setAttribute('width', container.clientWidth);
102                                 svg.setAttribute('height', container.clientHeight);
103                                 container.appendChild(svg);
104                         }
105                         this._afterLoad(container, svg, settings || {});
106                 }
107                 catch (e) {
108                         if ($.browser.msie) {
109                                 if (!container.id) {
110                                         container.id = 'svg' + (this._uuid++);
111                                 }
112                                 this._settings[container.id] = settings;
113                                 container.innerHTML = '<embed type="image/svg+xml" width="100%" ' +
114                                         'height="100%" src="' + (settings.initPath || '') + 'blank.svg"/>';
115                         }
116                         else {
117                                 container.innerHTML = '<p class="svg_error">' +
118                                         this.local.notSupportedText + '</p>';
119                         }
120                 }
121         },
123         /* SVG callback after loading - register SVG root. */
124         _registerSVG: function() {
125                 for (var i = 0; i < document.embeds.length; i++) { // Check all
126                         var container = document.embeds[i].parentNode;
127                         if (!$(container).hasClass($.svg.markerClassName) || // Not SVG
128                                         $.data(container, PROP_NAME)) { // Already done
129                                 continue;
130                         }
131                         var svg = null;
132                         try {
133                                 svg = document.embeds[i].getSVGDocument();
134                         }
135                         catch(e) {
136                                 setTimeout($.svg._registerSVG, 250); // Renesis takes longer to load
137                                 return;
138                         }
139                         svg = (svg ? svg.documentElement : null);
140                         if (svg) {
141                                 $.svg._afterLoad(container, svg);
142                         }
143                 }
144         },
146         /* Post-processing once loaded. */
147         _afterLoad: function(container, svg, settings) {
148                 var settings = settings || this._settings[container.id];
149                 this._settings[container ? container.id : ''] = null;
150                 var wrapper = new this._wrapperClass(svg, container);
151                 $.data(container || svg, PROP_NAME, wrapper);
152                 try {
153                         if (settings.loadURL) { // Load URL
154                                 wrapper.load(settings.loadURL, settings);
155                         }
156                         if (settings.settings) { // Additional settings
157                                 wrapper.configure(settings.settings);
158                         }
159                         if (settings.onLoad && !settings.loadURL) { // Onload callback
160                                 settings.onLoad.apply(container || svg, [wrapper]);
161                         }
162                 }
163                 catch (e) {
164                         alert(e);
165                 }
166         },
168         /* Return the SVG wrapper created for a given container.
169            @param  container  (string) selector for the container or
170                               (element) the container for the SVG object or
171                               jQuery collection - first entry is the container
172            @return  (SVGWrapper) the corresponding SVG wrapper element, or null if not attached */
173         _getSVG: function(container) {
174                 container = (typeof container == 'string' ? $(container)[0] :
175                         (container.jquery ? container[0] : container));
176                 return $.data(container, PROP_NAME);
177         },
179         /* Remove the SVG functionality from a div.
180            @param  container  (element) the container for the SVG object */
181         _destroySVG: function(container) {
182                 var $container = $(container);
183                 if (!$container.hasClass(this.markerClassName)) {
184                         return;
185                 }
186                 $container.removeClass(this.markerClassName);
187                 if (container.namespaceURI != this.svgNS) {
188                         $container.empty();
189                 }
190                 $.removeData(container, PROP_NAME);
191         },
193         /* Extend the SVGWrapper object with an embedded class.
194            The constructor function must take a single parameter that is
195            a reference to the owning SVG root object. This allows the 
196            extension to access the basic SVG functionality.
197            @param  name      (string) the name of the SVGWrapper attribute to access the new class
198            @param  extClass  (function) the extension class constructor */
199         addExtension: function(name, extClass) {
200                 this._extensions.push([name, extClass]);
201         }
204 /* The main SVG interface, which encapsulates the SVG element.
205    Obtain a reference from $().svg('get') */
206 function SVGWrapper(svg, container) {
207         this._svg = svg; // The SVG root node
208         this._container = container; // The containing div
209         for (var i = 0; i < $.svg._extensions.length; i++) {
210                 var extension = $.svg._extensions[i];
211                 this[extension[0]] = new extension[1](this);
212         }
215 $.extend(SVGWrapper.prototype, {
217         /* Retrieve the width of the SVG object. */
218         _width: function() {
219                 return (this._container ? this._container.clientWidth : this._svg.width);
220         },
222         /* Retrieve the height of the SVG object. */
223         _height: function() {
224                 return (this._container ? this._container.clientHeight : this._svg.height);
225         },
227         /* Retrieve the root SVG element.
228            @return  the top-level SVG element */
229         root: function() {
230                 return this._svg;
231         },
233         /* Configure the SVG root.
234            @param  settings  (object) additional settings for the root
235            @param  clear     (boolean) true to remove existing attributes first,
236                              false to add to what is already there (optional)
237            @return  (SVGWrapper) this root */
238         configure: function(settings, clear) {
239                 if (clear) {
240                         for (var i = this._svg.attributes.length - 1; i >= 0; i--) {
241                                 var attr = this._svg.attributes.item(i);
242                                 if (!(attr.nodeName == 'onload' || attr.nodeName == 'version' || 
243                                                 attr.nodeName.substring(0, 5) == 'xmlns')) {
244                                         this._svg.attributes.removeNamedItem(attr.nodeName);
245                                 }
246                         }
247                 }
248                 for (var attrName in settings) {
249                         this._svg.setAttribute(attrName, settings[attrName]);
250                 }
251                 return this;
252         },
254         /* Locate a specific element in the SVG document.
255            @param  id  (string) the element's identifier
256            @return  (element) the element reference, or null if not found */
257         getElementById: function(id) {
258                 return this._svg.ownerDocument.getElementById(id);
259         },
261         /* Change the attributes for a SVG node.
262            @param  element   (SVG element) the node to change
263            @param  settings  (object) the new settings
264            @return  (SVGWrapper) this root */
265         change: function(element, settings) {
266                 if (element) {
267                         for (var name in settings) {
268                                 if (settings[name] == null) {
269                                         element.removeAttribute(name);
270                                 }
271                                 else {
272                                         element.setAttribute(name, settings[name]);
273                                 }
274                         }
275                 }
276                 return this;
277         },
279         /* Check for parent being absent and adjust arguments accordingly. */
280         _args: function(values, names, optSettings) {
281                 names.splice(0, 0, 'parent');
282                 names.splice(names.length, 0, 'settings');
283                 var args = {};
284                 var offset = 0;
285                 if (values[0] != null && values[0].jquery) {
286                         values[0] = values[0][0];
287                 }
288                 if (values[0] != null && !(typeof values[0] == 'object' && values[0].nodeName)) {
289                         args['parent'] = null;
290                         offset = 1;
291                 }
292                 for (var i = 0; i < values.length; i++) {
293                         args[names[i + offset]] = values[i];
294                 }
295                 if (optSettings) {
296                         $.each(optSettings, function(i, value) {
297                                 if (typeof args[value] == 'object') {
298                                         args.settings = args[value];
299                                         args[value] = null;
300                                 }
301                         });
302                 }
303                 return args;
304         },
306         /* Add a title.
307            @param  parent    (element or jQuery) the parent node for the new title (optional)
308            @param  text      (string) the text of the title
309            @param  settings  (object) additional settings for the title (optional)
310            @return  (element) the new title node */
311         title: function(parent, text, settings) {
312                 var args = this._args(arguments, ['text']);
313                 var node = this._makeNode(args.parent, 'title', args.settings || {});
314                 node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
315                 return node;
316         },
318         /* Add a description.
319            @param  parent    (element or jQuery) the parent node for the new description (optional)
320            @param  text      (string) the text of the description
321            @param  settings  (object) additional settings for the description (optional)
322            @return  (element) the new description node */
323         describe: function(parent, text, settings) {
324                 var args = this._args(arguments, ['text']);
325                 var node = this._makeNode(args.parent, 'desc', args.settings || {});
326                 node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
327                 return node;
328         },
330         /* Add a definitions node.
331            @param  parent    (element or jQuery) the parent node for the new definitions (optional)
332            @param  id        (string) the ID of this definitions (optional)
333            @param  settings  (object) additional settings for the definitions (optional)
334            @return  (element) the new definitions node */
335         defs: function(parent, id, settings) {
336                 var args = this._args(arguments, ['id'], ['id']);
337                 return this._makeNode(args.parent, 'defs', $.extend(
338                         (args.id ? {id: args.id} : {}), args.settings || {}));
339         },
341         /* Add a symbol definition.
342            @param  parent    (element or jQuery) the parent node for the new symbol (optional)
343            @param  id        (string) the ID of this symbol
344            @param  x1        (number) the left coordinate for this symbol
345            @param  y1        (number) the top coordinate for this symbol
346            @param  width     (number) the width of this symbol
347            @param  height    (number) the height of this symbol
348            @param  settings  (object) additional settings for the symbol (optional)
349            @return  (element) the new symbol node */
350         symbol: function(parent, id, x1, y1, width, height, settings) {
351                 var args = this._args(arguments, ['id', 'x1', 'y1', 'width', 'height']);
352                 return this._makeNode(args.parent, 'symbol', $.extend({id: args.id,
353                         viewBox: args.x1 + ' ' + args.y1 + ' ' + args.width + ' ' + args.height},
354                         args.settings || {}));
355         },
357         /* Add a marker definition.
358            @param  parent    (element or jQuery) the parent node for the new marker (optional)
359            @param  id        (string) the ID of this marker
360            @param  refX      (number) the x-coordinate for the reference point
361            @param  refY      (number) the y-coordinate for the reference point
362            @param  mWidth    (number) the marker viewport width
363            @param  mHeight   (number) the marker viewport height
364            @param  orient    (string or int) 'auto' or angle (degrees) (optional)
365            @param  settings  (object) additional settings for the marker (optional)
366            @return  (element) the new marker node */
367         marker: function(parent, id, refX, refY, mWidth, mHeight, orient, settings) {
368                 var args = this._args(arguments, ['id', 'refX', 'refY',
369                         'mWidth', 'mHeight', 'orient'], ['orient']);
370                 return this._makeNode(args.parent, 'marker', $.extend(
371                         {id: args.id, refX: args.refX, refY: args.refY, markerWidth: args.mWidth, 
372                         markerHeight: args.mHeight, orient: args.orient || 'auto'}, args.settings || {}));
373         },
375         /* Add a style node.
376            @param  parent    (element or jQuery) the parent node for the new node (optional)
377            @param  styles    (string) the CSS styles
378            @param  settings  (object) additional settings for the node (optional)
379            @return  (element) the new style node */
380         style: function(parent, styles, settings) {
381                 var args = this._args(arguments, ['styles']);
382                 var node = this._makeNode(args.parent, 'style', $.extend(
383                         {type: 'text/css'}, args.settings || {}));
384                 node.appendChild(this._svg.ownerDocument.createTextNode(args.styles));
385                 if ($.browser.opera) {
386                         $('head').append('<style type="text/css">' + args.styles + '</style>');
387                 }
388                 return node;
389         },
391         /* Add a script node.
392            @param  parent    (element or jQuery) the parent node for the new node (optional)
393            @param  script    (string) the JavaScript code
394            @param  type      (string) the MIME type for the code (optional, default 'text/javascript')
395            @param  settings  (object) additional settings for the node (optional)
396            @return  (element) the new script node */
397         script: function(parent, script, type, settings) {
398                 var args = this._args(arguments, ['script', 'type'], ['type']);
399                 var node = this._makeNode(args.parent, 'script', $.extend(
400                         {type: args.type || 'text/javascript'}, args.settings || {}));
401                 node.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(args.script)));
402                 if (!$.browser.mozilla) {
403                         $.globalEval(args.script);
404                 }
405                 return node;
406         },
408         /* Add a linear gradient definition.
409            Specify all of x1, y1, x2, y2 or none of them.
410            @param  parent    (element or jQuery) the parent node for the new gradient (optional)
411            @param  id        (string) the ID for this gradient
412            @param  stops     (string[][]) the gradient stops, each entry is
413                              [0] is offset (0.0-1.0 or 0%-100%), [1] is colour, 
414                                                  [2] is opacity (optional)
415            @param  x1        (number) the x-coordinate of the gradient start (optional)
416            @param  y1        (number) the y-coordinate of the gradient start (optional)
417            @param  x2        (number) the x-coordinate of the gradient end (optional)
418            @param  y2        (number) the y-coordinate of the gradient end (optional)
419            @param  settings  (object) additional settings for the gradient (optional)
420            @return  (element) the new gradient node */
421         linearGradient: function(parent, id, stops, x1, y1, x2, y2, settings) {
422                 var args = this._args(arguments,
423                         ['id', 'stops', 'x1', 'y1', 'x2', 'y2'], ['x1']);
424                 var sets = $.extend({id: args.id}, 
425                         (args.x1 != null ? {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2} : {}));
426                 return this._gradient(args.parent, 'linearGradient', 
427                         $.extend(sets, args.settings || {}), args.stops);
428         },
430         /* Add a radial gradient definition.
431            Specify all of cx, cy, r, fx, fy or none of them.
432            @param  parent    (element or jQuery) the parent node for the new gradient (optional)
433            @param  id        (string) the ID for this gradient
434            @param  stops     (string[][]) the gradient stops, each entry
435                              [0] is offset, [1] is colour, [2] is opacity (optional)
436            @param  cx        (number) the x-coordinate of the largest circle centre (optional)
437            @param  cy        (number) the y-coordinate of the largest circle centre (optional)
438            @param  r         (number) the radius of the largest circle (optional)
439            @param  fx        (number) the x-coordinate of the gradient focus (optional)
440            @param  fy        (number) the y-coordinate of the gradient focus (optional)
441            @param  settings  (object) additional settings for the gradient (optional)
442            @return  (element) the new gradient node */
443         radialGradient: function(parent, id, stops, cx, cy, r, fx, fy, settings) {
444                 var args = this._args(arguments,
445                         ['id', 'stops', 'cx', 'cy', 'r', 'fx', 'fy'], ['cx']);
446                 var sets = $.extend({id: args.id}, (args.cx != null ?
447                         {cx: args.cx, cy: args.cy, r: args.r, fx: args.fx, fy: args.fy} : {}));
448                 return this._gradient(args.parent, 'radialGradient', 
449                         $.extend(sets, args.settings || {}), args.stops);
450         },
452         /* Add a gradient node. */
453         _gradient: function(parent, name, settings, stops) {
454                 var node = this._makeNode(parent, name, settings);
455                 for (var i = 0; i < stops.length; i++) {
456                         var stop = stops[i];
457                         this._makeNode(node, 'stop', $.extend(
458                                 {offset: stop[0], stopColor: stop[1]}, 
459                                 (stop[2] != null ? {stopOpacity: stop[2]} : {})));
460                 }
461                 return node;
462         },
464         /* Add a pattern definition.
465            Specify all of vx, vy, xwidth, vheight or none of them.
466            @param  parent    (element or jQuery) the parent node for the new pattern (optional)
467            @param  id        (string) the ID for this pattern
468            @param  x         (number) the x-coordinate for the left edge of the pattern
469            @param  y         (number) the y-coordinate for the top edge of the pattern
470            @param  width     (number) the width of the pattern
471            @param  height    (number) the height of the pattern
472            @param  vx        (number) the minimum x-coordinate for view box (optional)
473            @param  vy        (number) the minimum y-coordinate for the view box (optional)
474            @param  vwidth    (number) the width of the view box (optional)
475            @param  vheight   (number) the height of the view box (optional)
476            @param  settings  (object) additional settings for the pattern (optional)
477            @return  (element) the new pattern node */
478         pattern: function(parent, id, x, y, width, height, vx, vy, vwidth, vheight, settings) {
479                 var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height',
480                         'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
481                 var sets = $.extend({id: args.id, x: args.x, y: args.y,
482                         width: args.width, height: args.height}, (args.vx != null ?
483                         {viewBox: args.vx + ' ' + args.vy + ' ' + args.vwidth + ' ' + args.vheight} : {}));
484                 return this._makeNode(args.parent, 'pattern', $.extend(sets, args.settings || {}));
485         },
487         /* Add a mask definition.
488            @param  parent    (element or jQuery) the parent node for the new mask (optional)
489            @param  id        (string) the ID for this mask
490            @param  x         (number) the x-coordinate for the left edge of the mask
491            @param  y         (number) the y-coordinate for the top edge of the mask
492            @param  width     (number) the width of the mask
493            @param  height    (number) the height of the mask
494            @param  settings  (object) additional settings for the mask (optional)
495            @return  (element) the new mask node */
496         mask: function(parent, id, x, y, width, height, settings) {
497                 var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height']);
498                 return this._makeNode(args.parent, 'mask', $.extend(
499                         {id: args.id, x: args.x, y: args.y, width: args.width, height: args.height},
500                         args.settings || {}));
501         },
503         /* Create a new path object.
504            @return  (SVGPath) a new path object */
505         createPath: function() {
506                 return new SVGPath();
507         },
509         /* Create a new text object.
510            @return  (SVGText) a new text object */
511         createText: function() {
512                 return new SVGText();
513         },
515         /* Add an embedded SVG element.
516            Specify all of vx, vy, vwidth, vheight or none of them.
517            @param  parent    (element or jQuery) the parent node for the new node (optional)
518            @param  x         (number) the x-coordinate for the left edge of the node
519            @param  y         (number) the y-coordinate for the top edge of the node
520            @param  width     (number) the width of the node
521            @param  height    (number) the height of the node
522            @param  vx        (number) the minimum x-coordinate for view box (optional)
523            @param  vy        (number) the minimum y-coordinate for the view box (optional)
524            @param  vwidth    (number) the width of the view box (optional)
525            @param  vheight   (number) the height of the view box (optional)
526            @param  settings  (object) additional settings for the node (optional)
527            @return  (element) the new node */
528         svg: function(parent, x, y, width, height, vx, vy, vwidth, vheight, settings) {
529                 var args = this._args(arguments, ['x', 'y', 'width', 'height',
530                         'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
531                 var sets = $.extend({x: args.x, y: args.y, width: args.width, height: args.height}, 
532                         (args.vx != null ? {viewBox: args.vx + ' ' + args.vy + ' ' +
533                         args.vwidth + ' ' + args.vheight} : {}));
534                 return this._makeNode(args.parent, 'svg', $.extend(sets, args.settings || {}));
535         },
537         /* Create a group.
538            @param  parent    (element or jQuery) the parent node for the new group (optional)
539            @param  id        (string) the ID of this group (optional)
540            @param  settings  (object) additional settings for the group (optional)
541            @return  (element) the new group node */
542         group: function(parent, id, settings) {
543                 var args = this._args(arguments, ['id'], ['id']);
544                 return this._makeNode(args.parent, 'g', $.extend({id: args.id}, args.settings || {}));
545         },
547         /* Add a usage reference.
548            Specify all of x, y, width, height or none of them.
549            @param  parent    (element or jQuery) the parent node for the new node (optional)
550            @param  x         (number) the x-coordinate for the left edge of the node (optional)
551            @param  y         (number) the y-coordinate for the top edge of the node (optional)
552            @param  width     (number) the width of the node (optional)
553            @param  height    (number) the height of the node (optional)
554            @param  ref       (string) the ID of the definition node
555            @param  settings  (object) additional settings for the node (optional)
556            @return  (element) the new node */
557         use: function(parent, x, y, width, height, ref, settings) {
558                 var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
559                 if (typeof args.x == 'string') {
560                         args.ref = args.x;
561                         args.settings = args.y;
562                         args.x = args.y = args.width = args.height = null;
563                 }
564                 var node = this._makeNode(args.parent, 'use', $.extend(
565                         {x: args.x, y: args.y, width: args.width, height: args.height},
566                         args.settings || {}));
567                 node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
568                 return node;
569         },
571         /* Add a link, which applies to all child elements.
572            @param  parent    (element or jQuery) the parent node for the new link (optional)
573            @param  ref       (string) the target URL
574            @param  settings  (object) additional settings for the link (optional)
575            @return  (element) the new link node */
576         link: function(parent, ref, settings) {
577                 var args = this._args(arguments, ['ref']);
578                 var node = this._makeNode(args.parent, 'a', args.settings);
579                 node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
580                 return node;
581         },
583         /* Add an image.
584            @param  parent    (element or jQuery) the parent node for the new image (optional)
585            @param  x         (number) the x-coordinate for the left edge of the image
586            @param  y         (number) the y-coordinate for the top edge of the image
587            @param  width     (number) the width of the image
588            @param  height    (number) the height of the image
589            @param  ref       (string) the path to the image
590            @param  settings  (object) additional settings for the image (optional)
591            @return  (element) the new image node */
592         image: function(parent, x, y, width, height, ref, settings) {
593                 var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
594                 var node = this._makeNode(args.parent, 'image', $.extend(
595                         {x: args.x, y: args.y, width: args.width, height: args.height},
596                         args.settings || {}));
597                 node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
598                 return node;
599         },
601         /* Draw a path.
602            @param  parent    (element or jQuery) the parent node for the new shape (optional)
603            @param  path      (string or SVGPath) the path to draw
604            @param  settings  (object) additional settings for the shape (optional)
605            @return  (element) the new shape node */
606         path: function(parent, path, settings) {
607                 var args = this._args(arguments, ['path']);
608                 return this._makeNode(args.parent, 'path', $.extend(
609                         {d: (args.path.path ? args.path.path() : args.path)}, args.settings || {}));
610         },
612         /* Draw a rectangle.
613            Specify both of rx and ry or neither.
614            @param  parent    (element or jQuery) the parent node for the new shape (optional)
615            @param  x         (number) the x-coordinate for the left edge of the rectangle
616            @param  y         (number) the y-coordinate for the top edge of the rectangle
617            @param  width     (number) the width of the rectangle
618            @param  height    (number) the height of the rectangle
619            @param  rx        (number) the x-radius of the ellipse for the rounded corners (optional)
620            @param  ry        (number) the y-radius of the ellipse for the rounded corners (optional)
621            @param  settings  (object) additional settings for the shape (optional)
622            @return  (element) the new shape node */
623         rect: function(parent, x, y, width, height, rx, ry, settings) {
624                 var args = this._args(arguments, ['x', 'y', 'width', 'height', 'rx', 'ry'], ['rx']);
625                 return this._makeNode(args.parent, 'rect', $.extend(
626                         {x: args.x, y: args.y, width: args.width, height: args.height},
627                         (args.rx ? {rx: args.rx, ry: args.ry} : {}), args.settings || {}));
628         },
630         /* Draw a circle.
631            @param  parent    (element or jQuery) the parent node for the new shape (optional)
632            @param  cx        (number) the x-coordinate for the centre of the circle
633            @param  cy        (number) the y-coordinate for the centre of the circle
634            @param  r         (number) the radius of the circle
635            @param  settings  (object) additional settings for the shape (optional)
636            @return  (element) the new shape node */
637         circle: function(parent, cx, cy, r, settings) {
638                 var args = this._args(arguments, ['cx', 'cy', 'r']);
639                 return this._makeNode(args.parent, 'circle', $.extend(
640                         {cx: args.cx, cy: args.cy, r: args.r}, args.settings || {}));
641         },
643         /* Draw an ellipse.
644            @param  parent    (element or jQuery) the parent node for the new shape (optional)
645            @param  cx        (number) the x-coordinate for the centre of the ellipse
646            @param  cy        (number) the y-coordinate for the centre of the ellipse
647            @param  rx        (number) the x-radius of the ellipse
648            @param  ry        (number) the y-radius of the ellipse
649            @param  settings  (object) additional settings for the shape (optional)
650            @return  (element) the new shape node */
651         ellipse: function(parent, cx, cy, rx, ry, settings) {
652                 var args = this._args(arguments, ['cx', 'cy', 'rx', 'ry']);
653                 return this._makeNode(args.parent, 'ellipse', $.extend(
654                         {cx: args.cx, cy: args.cy, rx: args.rx, ry: args.ry}, args.settings || {}));
655         },
657         /* Draw a line.
658            @param  parent    (element or jQuery) the parent node for the new shape (optional)
659            @param  x1        (number) the x-coordinate for the start of the line
660            @param  y1        (number) the y-coordinate for the start of the line
661            @param  x2        (number) the x-coordinate for the end of the line
662            @param  y2        (number) the y-coordinate for the end of the line
663            @param  settings  (object) additional settings for the shape (optional)
664            @return  (element) the new shape node */
665         line: function(parent, x1, y1, x2, y2, settings) {
666                 var args = this._args(arguments, ['x1', 'y1', 'x2', 'y2']);
667                 return this._makeNode(args.parent, 'line', $.extend(
668                         {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2}, args.settings || {}));
669         },
671         /* Draw a polygonal line.
672            @param  parent    (element or jQuery) the parent node for the new shape (optional)
673            @param  points    (number[][]) the x-/y-coordinates for the points on the line
674            @param  settings  (object) additional settings for the shape (optional)
675            @return  (element) the new shape node */
676         polyline: function(parent, points, settings) {
677                 var args = this._args(arguments, ['points']);
678                 return this._poly(args.parent, 'polyline', args.points, args.settings);
679         },
681         /* Draw a polygonal shape.
682            @param  parent    (element or jQuery) the parent node for the new shape (optional)
683            @param  points    (number[][]) the x-/y-coordinates for the points on the shape
684            @param  settings  (object) additional settings for the shape (optional)
685            @return  (element) the new shape node */
686         polygon: function(parent, points, settings) {
687                 var args = this._args(arguments, ['points']);
688                 return this._poly(args.parent, 'polygon', args.points, args.settings);
689         },
691         /* Draw a polygonal line or shape. */
692         _poly: function(parent, name, points, settings) {
693                 var ps = '';
694                 for (var i = 0; i < points.length; i++) {
695                         ps += points[i].join() + ' ';
696                 }
697                 return this._makeNode(parent, name, $.extend(
698                         {points: $.trim(ps)}, settings || {}));
699         },
701         /* Draw text.
702            Specify both of x and y or neither of them.
703            @param  parent    (element or jQuery) the parent node for the text (optional)
704            @param  x         (number or number[]) the x-coordinate(s) for the text (optional)
705            @param  y         (number or number[]) the y-coordinate(s) for the text (optional)
706            @param  value     (string) the text content or
707                              (SVGText) text with spans and references
708            @param  settings  (object) additional settings for the text (optional)
709            @return  (element) the new text node */
710         text: function(parent, x, y, value, settings) {
711                 var args = this._args(arguments, ['x', 'y', 'value']);
712                 if (typeof args.x == 'string' && arguments.length < 4) {
713                         args.value = args.x;
714                         args.settings = args.y;
715                         args.x = args.y = null;
716                 }
717                 return this._text(args.parent, 'text', args.value, $.extend(
718                         {x: (args.x && isArray(args.x) ? args.x.join(' ') : args.x),
719                         y: (args.y && isArray(args.y) ? args.y.join(' ') : args.y)}, 
720                         args.settings || {}));
721         },
723         /* Draw text along a path.
724            @param  parent    (element or jQuery) the parent node for the text (optional)
725            @param  path      (string) the ID of the path
726            @param  value     (string) the text content or
727                              (SVGText) text with spans and references
728            @param  settings  (object) additional settings for the text (optional)
729            @return  (element) the new text node */
730         textpath: function(parent, path, value, settings) {
731                 var args = this._args(arguments, ['path', 'value']);
732                 var node = this._text(args.parent, 'textPath', args.value, args.settings || {});
733                 node.setAttributeNS($.svg.xlinkNS, 'href', args.path);
734                 return node;
735         },
737         /* Draw text. */
738         _text: function(parent, name, value, settings) {
739                 var node = this._makeNode(parent, name, settings);
740                 if (typeof value == 'string') {
741                         node.appendChild(node.ownerDocument.createTextNode(value));
742                 }
743                 else {
744                         for (var i = 0; i < value._parts.length; i++) {
745                                 var part = value._parts[i];
746                                 if (part[0] == 'tspan') {
747                                         var child = this._makeNode(node, part[0], part[2]);
748                                         child.appendChild(node.ownerDocument.createTextNode(part[1]));
749                                         node.appendChild(child);
750                                 }
751                                 else if (part[0] == 'tref') {
752                                         var child = this._makeNode(node, part[0], part[2]);
753                                         child.setAttributeNS($.svg.xlinkNS, 'href', part[1]);
754                                         node.appendChild(child);
755                                 }
756                                 else if (part[0] == 'textpath') {
757                                         var set = $.extend({}, part[2]);
758                                         set.href = null;
759                                         var child = this._makeNode(node, part[0], set);
760                                         child.setAttributeNS($.svg.xlinkNS, 'href', part[2].href);
761                                         child.appendChild(node.ownerDocument.createTextNode(part[1]));
762                                         node.appendChild(child);
763                                 }
764                                 else { // straight text
765                                         node.appendChild(node.ownerDocument.createTextNode(part[1]));
766                                 }
767                         }
768                 }
769                 return node;
770         },
772         /* Add a custom SVG element.
773            @param  parent    (element or jQuery) the parent node for the new element (optional)
774            @param  name      (string) the name of the element
775            @param  settings  (object) additional settings for the element (optional)
776            @return  (element) the new custom node */
777         other: function(parent, name, settings) {
778                 var args = this._args(arguments, ['name']);
779                 return this._makeNode(args.parent, args.name, args.settings || {});
780         },
782         /* Create a shape node with the given settings. */
783         _makeNode: function(parent, name, settings) {
784                 parent = parent || this._svg;
785                 var node = this._svg.ownerDocument.createElementNS($.svg.svgNS, name);
786                 for (var name in settings) {
787                         var value = settings[name];
788                         if (value != null && value != null && 
789                                         (typeof value != 'string' || value != '')) {
790                                 node.setAttribute($.svg._attrNames[name] || name, value);
791                         }
792                 }
793                 parent.appendChild(node);
794                 return node;
795         },
797         /* Add an existing SVG node to the diagram.
798            @param  parent  (element or jQuery) the parent node for the new node (optional)
799            @param  node    (element) the new node to add or
800                            (string) the jQuery selector for the node or
801                            (jQuery collection) set of nodes to add
802            @return  (SVGWrapper) this wrapper */
803         add: function(parent, node) {
804                 var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']);
805                 var svg = this;
806                 args.parent = args.parent || this._svg;
807                 try {
808                         if ($.svg._renesis) {
809                                 throw 'Force traversal';
810                         }
811                         args.parent.appendChild(args.node.cloneNode(true));
812                 }
813                 catch (e) {
814                         args.node = (args.node.jquery ? args.node : $(args.node));
815                         args.node.each(function() {
816                                 var child = svg._cloneAsSVG(this);
817                                 if (child) {
818                                         args.parent.appendChild(child);
819                                 }
820                         });
821                 }
822                 return this;
823         },
825         /* SVG nodes must belong to the SVG namespace, so clone and ensure this is so. */
826         _cloneAsSVG: function(node) {
827                 var newNode = null;
828                 if (node.nodeType == 1) { // element
829                         newNode = this._svg.ownerDocument.createElementNS(
830                                 $.svg.svgNS, this._checkName(node.nodeName));
831                         for (var i = 0; i < node.attributes.length; i++) {
832                                 var attr = node.attributes.item(i);
833                                 if (attr.nodeName != 'xmlns' && attr.nodeValue) {
834                                         if (attr.prefix == 'xlink') {
835                                                 newNode.setAttributeNS($.svg.xlinkNS, attr.localName, attr.nodeValue);
836                                         }
837                                         else {
838                                                 newNode.setAttribute(this._checkName(attr.nodeName), attr.nodeValue);
839                                         }
840                                 }
841                         }
842                         for (var i = 0; i < node.childNodes.length; i++) {
843                                 var child = this._cloneAsSVG(node.childNodes[i]);
844                                 if (child) {
845                                         newNode.appendChild(child);
846                                 }
847                         }
848                 }
849                 else if (node.nodeType == 3) { // text
850                         if ($.trim(node.nodeValue)) {
851                                 newNode = this._svg.ownerDocument.createTextNode(node.nodeValue);
852                         }
853                 }
854                 else if (node.nodeType == 4) { // CDATA
855                         if ($.trim(node.nodeValue)) {
856                                 try {
857                                         newNode = this._svg.ownerDocument.createCDATASection(node.nodeValue);
858                                 }
859                                 catch (e) {
860                                         newNode = this._svg.ownerDocument.createTextNode(
861                                                 node.nodeValue.replace(/&/g, '&amp;').
862                                                 replace(/</g, '&lt;').replace(/>/g, '&gt;'));
863                                 }
864                         }
865                 }
866                 return newNode;
867         },
869         /* Node names must be lower case and without SVG namespace prefix. */
870         _checkName: function(name) {
871                 name = (name.substring(0, 1) >= 'A' && name.substring(0, 1) <= 'Z' ?
872                         name.toLowerCase() : name);
873                 return (name.substring(0, 4) == 'svg:' ? name.substring(4) : name);
874         },
876         /* Load an external SVG document.
877            @param  url       (string) the location of the SVG document or
878                              the actual SVG content
879            @param  settings  (boolean) see addTo below or
880                              (function) see onLoad below or
881                              (object) additional settings for the load with attributes below:
882                                addTo       (boolean) true to add to what's already there,
883                                            or false to clear the canvas first
884                                                    changeSize  (boolean) true to allow the canvas size to change,
885                                            or false to retain the original
886                                onLoad      (function) callback after the document has loaded,
887                                            'this' is the container, receives SVG object and
888                                            optional error message as a parameter
889            @return  (SVGWrapper) this root */
890         load: function(url, settings) {
891                 settings = (typeof settings == 'boolean'? {addTo: settings} :
892                         (typeof settings == 'function'? {onLoad: settings} : settings || {}));
893                 if (!settings.addTo) {
894                         this.clear(false);
895                 }
896                 var size = [this._svg.getAttribute('width'), this._svg.getAttribute('height')];
897                 var wrapper = this;
898                 // Report a problem with the load
899                 var reportError = function(message) {
900                         message = $.svg.local.errorLoadingText + ': ' + message;
901                         if (settings.onLoad) {
902                                 settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper, message]);
903                         }
904                         else {
905                                 wrapper.text(null, 10, 20, message);
906                         }
907                 };
908                 // Create a DOM from SVG content
909                 var loadXML4IE = function(data) {
910                         var xml = new ActiveXObject('Microsoft.XMLDOM');
911                         xml.validateOnParse = false;
912                         xml.resolveExternals = false;
913                         xml.async = false;
914                         xml.loadXML(data);
915                         if (xml.parseError.errorCode != 0) {
916                                 reportError(xml.parseError.reason);
917                                 return null;
918                         }
919                         return xml;
920                 };
921                 // Load the SVG DOM
922                 var loadSVG = function(data) {
923                         if (!data) {
924                                 return;
925                         }
926                         if (data.documentElement.nodeName != 'svg') {
927                                 var errors = data.getElementsByTagName('parsererror');
928                                 var messages = (errors.length ? errors[0].getElementsByTagName('div') : []); // Safari
929                                 reportError(!errors.length ? '???' :
930                                         (messages.length ? messages[0] : errors[0]).firstChild.nodeValue);
931                                 return;
932                         }
933                         var attrs = {};
934                         for (var i = 0; i < data.documentElement.attributes.length; i++) {
935                                 var attr = data.documentElement.attributes.item(i);
936                                 if (!(attr.nodeName == 'version' || attr.nodeName.substring(0, 5) == 'xmlns')) {
937                                         attrs[attr.nodeName] = attr.nodeValue;
938                                 }
939                         }
940                         wrapper.configure(attrs, true);
941                         var nodes = data.documentElement.childNodes;
942                         for (var i = 0; i < nodes.length; i++) {
943                                 try {
944                                         if ($.svg._renesis) {
945                                                 throw 'Force traversal';
946                                         }
947                                         wrapper._svg.appendChild(nodes[i].cloneNode(true));
948                                         if (nodes[i].nodeName == 'script') {
949                                                 $.globalEval(nodes[i].textContent);
950                                         }
951                                 }
952                                 catch (e) {
953                                         wrapper.add(null, nodes[i]);
954                                 }
955                         }
956                         if (!settings.changeSize) {
957                                 wrapper.configure({width: size[0], height: size[1]});
958                         }
959                         if (settings.onLoad) {
960                                 settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper]);
961                         }
962                 };
963                 if (url.match('<svg')) { // Inline SVG
964                         loadSVG($.browser.msie ? loadXML4IE(url) :
965                                 new DOMParser().parseFromString(url, 'text/xml'));
966                 }
967                 else { // Remote SVG
968                         $.ajax({url: url, dataType: ($.browser.msie ? 'text' : 'xml'),
969                                 success: function(xml) {
970                                         loadSVG($.browser.msie ? loadXML4IE(xml) : xml);
971                                 }, error: function(http, message, exc) {
972                                         reportError(message + (exc ? ' ' + exc.message : ''));
973                                 }});
974                 }
975                 return this;
976         },
978         /* Delete a specified node.
979            @param  node  (element or jQuery) the drawing node to remove
980            @return  (SVGWrapper) this root */
981         remove: function(node) {
982                 node = (node.jquery ? node[0] : node);
983                 node.parentNode.removeChild(node);
984                 return this;
985         },
987         /* Delete everything in the current document.
988            @param  attrsToo  (boolean) true to clear any root attributes as well,
989                              false to leave them (optional)
990            @return  (SVGWrapper) this root */
991         clear: function(attrsToo) {
992                 if (attrsToo) {
993                         this.configure({}, true);
994                 }
995                 while (this._svg.firstChild) {
996                         this._svg.removeChild(this._svg.firstChild);
997                 }
998                 return this;
999         },
1001         /* Serialise the current diagram into an SVG text document.
1002            @param  node  (SVG element) the starting node (optional)
1003            @return  (string) the SVG as text */
1004         toSVG: function(node) {
1005                 node = node || this._svg;
1006                 return (typeof XMLSerializer == 'undefined' ? this._toSVG(node) :
1007                         new XMLSerializer().serializeToString(node));
1008         },
1010         /* Serialise one node in the SVG hierarchy. */
1011         _toSVG: function(node) {
1012                 var svgDoc = '';
1013                 if (!node) {
1014                         return svgDoc;
1015                 }
1016                 if (node.nodeType == 3) { // Text
1017                         svgDoc = node.nodeValue;
1018                 }
1019                 else if (node.nodeType == 4) { // CDATA
1020                         svgDoc = '<![CDATA[' + node.nodeValue + ']]>';
1021                 }
1022                 else { // Element
1023                         svgDoc = '<' + node.nodeName;
1024                         if (node.attributes) {
1025                                 for (var i = 0; i < node.attributes.length; i++) {
1026                                         var attr = node.attributes.item(i);
1027                                         if (!($.trim(attr.nodeValue) == '' || attr.nodeValue.match(/^\[object/) ||
1028                                                         attr.nodeValue.match(/^function/))) {
1029                                                 svgDoc += ' ' + (attr.namespaceURI == $.svg.xlinkNS ? 'xlink:' : '') + 
1030                                                         attr.nodeName + '="' + attr.nodeValue + '"';
1031                                         }
1032                                 }
1033                         }       
1034                         if (node.firstChild) {
1035                                 svgDoc += '>';
1036                                 var child = node.firstChild;
1037                                 while (child) {
1038                                         svgDoc += this._toSVG(child);
1039                                         child = child.nextSibling;
1040                                 }
1041                                 svgDoc += '</' + node.nodeName + '>';
1042                         }
1043                                 else {
1044                                 svgDoc += '/>';
1045                         }
1046                 }
1047                 return svgDoc;
1048         },
1049         
1050         /* Escape reserved characters in XML. */
1051         _escapeXML: function(text) {
1052                 text = text.replace(/&/g, '&amp;');
1053                 text = text.replace(/</g, '&lt;');
1054                 text = text.replace(/>/g, '&gt;');
1055                 return text;
1056         }
1059 /* Helper to generate an SVG path.
1060    Obtain an instance from the SVGWrapper object.
1061    String calls together to generate the path and use its value:
1062    var path = root.createPath();
1063    root.path(null, path.move(100, 100).line(300, 100).line(200, 300).close(), {fill: 'red'});
1064    or
1065    root.path(null, path.move(100, 100).line([[300, 100], [200, 300]]).close(), {fill: 'red'}); */
1066 function SVGPath() {
1067         this._path = '';
1070 $.extend(SVGPath.prototype, {
1071         /* Prepare to create a new path.
1072            @return  (SVGPath) this path */
1073         reset: function() {
1074                 this._path = '';
1075                 return this;
1076         },
1078         /* Move the pointer to a position.
1079            @param  x         (number) x-coordinate to move to or
1080                              (number[][]) x-/y-coordinates to move to
1081            @param  y         (number) y-coordinate to move to (omitted if x is array)
1082            @param  relative  (boolean) true for coordinates relative to the current point,
1083                              false for coordinates being absolute
1084            @return  (SVGPath) this path */
1085         move: function(x, y, relative) {
1086                 relative = (isArray(x) ? y : relative);
1087                 return this._coords((relative ? 'm' : 'M'), x, y);
1088         },
1090         /* Draw a line to a position.
1091            @param  x         (number) x-coordinate to move to or
1092                              (number[][]) x-/y-coordinates to move to
1093            @param  y         (number) y-coordinate to move to (omitted if x is array)
1094            @param  relative  (boolean) true for coordinates relative to the current point,
1095                              false for coordinates being absolute
1096            @return  (SVGPath) this path */
1097         line: function(x, y, relative) {
1098                 relative = (isArray(x) ? y : relative);
1099                 return this._coords((relative ? 'l' : 'L'), x, y);
1100         },
1102         /* Draw a horizontal line to a position.
1103            @param  x         (number) x-coordinate to draw to or
1104                              (number[]) x-coordinates to draw to
1105            @param  relative  (boolean) true for coordinates relative to the current point,
1106                              false for coordinates being absolute
1107            @return  (SVGPath) this path */
1108         horiz: function(x, relative) {
1109                 this._path += (relative ? 'h' : 'H') + (isArray(x) ? x.join(' ') : x);
1110                 return this;
1111         },
1113         /* Draw a vertical line to a position.
1114            @param  y         (number) y-coordinate to draw to or
1115                              (number[]) y-coordinates to draw to
1116            @param  relative  (boolean) true for coordinates relative to the current point,
1117                              false for coordinates being absolute
1118            @return  (SVGPath) this path */
1119         vert: function(y, relative) {
1120                 this._path += (relative ? 'v' : 'V') + (isArray(y) ? y.join(' ') : y);
1121                 return this;
1122         },
1124         /* Draw a cubic Bézier curve.
1125            @param  x1        (number) x-coordinate of beginning control point or
1126                              (number[][]) x-/y-coordinates of control and end points to draw to
1127            @param  y1        (number) y-coordinate of beginning control point (omitted if x1 is array)
1128            @param  x2        (number) x-coordinate of ending control point (omitted if x1 is array)
1129            @param  y2        (number) y-coordinate of ending control point (omitted if x1 is array)
1130            @param  x         (number) x-coordinate of curve end (omitted if x1 is array)
1131            @param  y         (number) y-coordinate of curve end (omitted if x1 is array)
1132            @param  relative  (boolean) true for coordinates relative to the current point,
1133                              false for coordinates being absolute
1134            @return  (SVGPath) this path */
1135         curveC: function(x1, y1, x2, y2, x, y, relative) {
1136                 relative = (isArray(x1) ? y1 : relative);
1137                 return this._coords((relative ? 'c' : 'C'), x1, y1, x2, y2, x, y);
1138         },
1140         /* Continue a cubic Bézier curve.
1141            Starting control point is the reflection of the previous end control point.
1142            @param  x2        (number) x-coordinate of ending control point or
1143                              (number[][]) x-/y-coordinates of control and end points to draw to
1144            @param  y2        (number) y-coordinate of ending control point (omitted if x2 is array)
1145            @param  x         (number) x-coordinate of curve end (omitted if x2 is array)
1146            @param  y         (number) y-coordinate of curve end (omitted if x2 is array)
1147            @param  relative  (boolean) true for coordinates relative to the current point,
1148                              false for coordinates being absolute
1149            @return  (SVGPath) this path */
1150         smoothC: function(x2, y2, x, y, relative) {
1151                 relative = (isArray(x2) ? y2 : relative);
1152                 return this._coords((relative ? 's' : 'S'), x2, y2, x, y);
1153         },
1155         /* Draw a quadratic Bézier curve.
1156            @param  x1        (number) x-coordinate of control point or
1157                              (number[][]) x-/y-coordinates of control and end points to draw to
1158            @param  y1        (number) y-coordinate of control point (omitted if x1 is array)
1159            @param  x         (number) x-coordinate of curve end (omitted if x1 is array)
1160            @param  y         (number) y-coordinate of curve end (omitted if x1 is array)
1161            @param  relative  (boolean) true for coordinates relative to the current point,
1162                              false for coordinates being absolute
1163            @return  (SVGPath) this path */
1164         curveQ: function(x1, y1, x, y, relative) {
1165                 relative = (isArray(x1) ? y1 : relative);
1166                 return this._coords((relative ? 'q' : 'Q'), x1, y1, x, y);
1167         },
1169         /* Continue a quadratic Bézier curve.
1170            Control point is the reflection of the previous control point.
1171            @param  x         (number) x-coordinate of curve end or
1172                              (number[][]) x-/y-coordinates of points to draw to
1173            @param  y         (number) y-coordinate of curve end (omitted if x is array)
1174            @param  relative  (boolean) true for coordinates relative to the current point,
1175                              false for coordinates being absolute
1176            @return  (SVGPath) this path */
1177         smoothQ: function(x, y, relative) {
1178                 relative = (isArray(x) ? y : relative);
1179                 return this._coords((relative ? 't' : 'T'), x, y);
1180         },
1182         /* Generate a path command with (a list of) coordinates. */
1183         _coords: function(cmd, x1, y1, x2, y2, x3, y3) {
1184                 if (isArray(x1)) {
1185                         for (var i = 0; i < x1.length; i++) {
1186                                 var cs = x1[i];
1187                                 this._path += (i == 0 ? cmd : ' ') + cs[0] + ',' + cs[1] +
1188                                         (cs.length < 4 ? '' : ' ' + cs[2] + ',' + cs[3] +
1189                                         (cs.length < 6 ? '': ' ' + cs[4] + ',' + cs[5]));
1190                         }
1191                 }
1192                 else {
1193                         this._path += cmd + x1 + ',' + y1 + 
1194                                 (x2 == null ? '' : ' ' + x2 + ',' + y2 +
1195                                 (x3 == null ? '' : ' ' + x3 + ',' + y3));
1196                 }
1197                 return this;
1198         },
1200         /* Draw an arc to a position.
1201            @param  rx         (number) x-radius of arc or
1202                               (number/boolean[][]) x-/y-coordinates and flags for points to draw to
1203            @param  ry         (number) y-radius of arc (omitted if rx is array)
1204            @param  xRotate    (number) x-axis rotation (degrees, clockwise) (omitted if rx is array)
1205            @param  large      (boolean) true to draw the large part of the arc,
1206                               false to draw the small part (omitted if rx is array)
1207            @param  clockwise  (boolean) true to draw the clockwise arc,
1208                               false to draw the anti-clockwise arc (omitted if rx is array)
1209            @param  x          (number) x-coordinate of arc end (omitted if rx is array)
1210            @param  y          (number) y-coordinate of arc end (omitted if rx is array)
1211            @param  relative   (boolean) true for coordinates relative to the current point,
1212                               false for coordinates being absolute
1213            @return  (SVGPath) this path */
1214         arc: function(rx, ry, xRotate, large, clockwise, x, y, relative) {
1215                 relative = (isArray(rx) ? ry : relative);
1216                 this._path += (relative ? 'a' : 'A');
1217                 if (isArray(rx)) {
1218                         for (var i = 0; i < rx.length; i++) {
1219                                 var cs = rx[i];
1220                                 this._path += (i == 0 ? '' : ' ') + cs[0] + ',' + cs[1] + ' ' +
1221                                         cs[2] + ' ' + (cs[3] ? '1' : '0') + ',' +
1222                                         (cs[4] ? '1' : '0') + ' ' + cs[5] + ',' + cs[6];
1223                         }
1224                 }
1225                 else {
1226                         this._path += rx + ',' + ry + ' ' + xRotate + ' ' +
1227                                 (large ? '1' : '0') + ',' + (clockwise ? '1' : '0') + ' ' + x + ',' + y;
1228                 }
1229                 return this;
1230         },
1232         /* Close the current path.
1233            @return  (SVGPath) this path */
1234         close: function() {
1235                 this._path += 'z';
1236                 return this;
1237         },
1239         /* Return the string rendering of the specified path.
1240            @return  (string) stringified path */
1241         path: function() {
1242                 return this._path;
1243         }
1246 SVGPath.prototype.moveTo = SVGPath.prototype.move;
1247 SVGPath.prototype.lineTo = SVGPath.prototype.line;
1248 SVGPath.prototype.horizTo = SVGPath.prototype.horiz;
1249 SVGPath.prototype.vertTo = SVGPath.prototype.vert;
1250 SVGPath.prototype.curveCTo = SVGPath.prototype.curveC;
1251 SVGPath.prototype.smoothCTo = SVGPath.prototype.smoothC;
1252 SVGPath.prototype.curveQTo = SVGPath.prototype.curveQ;
1253 SVGPath.prototype.smoothQTo = SVGPath.prototype.smoothQ;
1254 SVGPath.prototype.arcTo = SVGPath.prototype.arc;
1256 /* Helper to generate an SVG text object.
1257    Obtain an instance from the SVGWrapper object.
1258    String calls together to generate the text and use its value:
1259    var text = root.createText();
1260    root.text(null, x, y, text.string('This is ').
1261      span('red', {fill: 'red'}).string('!'), {fill: 'blue'}); */
1262 function SVGText() {
1263         this._parts = []; // The components of the text object
1266 $.extend(SVGText.prototype, {
1267         /* Prepare to create a new text object.
1268            @return  (SVGText) this text */
1269         reset: function() {
1270                 this._parts = [];
1271                 return this;
1272         },
1274         /* Add a straight string value.
1275            @param  value  (string) the actual text
1276            @return  (SVGText) this text object */
1277         string: function(value) {
1278                 this._parts[this._parts.length] = ['text', value];
1279                 return this;
1280         },
1282         /* Add a separate text span that has its own settings.
1283            @param  value     (string) the actual text
1284            @param  settings  (object) the settings for this text
1285            @return  (SVGText) this text object */
1286         span: function(value, settings) {
1287                 this._parts[this._parts.length] = ['tspan', value, settings];
1288                 return this;
1289         },
1291         /* Add a reference to a previously defined text string.
1292            @param  id        (string) the ID of the actual text
1293            @param  settings  (object) the settings for this text
1294            @return  (SVGText) this text object */
1295         ref: function(id, settings) {
1296                 this._parts[this._parts.length] = ['tref', id, settings];
1297                 return this;
1298         },
1300         /* Add text drawn along a path.
1301            @param  id        (string) the ID of the path
1302            @param  value     (string) the actual text
1303            @param  settings  (object) the settings for this text
1304            @return  (SVGText) this text object */
1305         path: function(id, value, settings) {
1306                 this._parts[this._parts.length] = ['textpath', value, 
1307                         $.extend({href: id}, settings || {})];
1308                 return this;
1309         }
1312 /* Attach the SVG functionality to a jQuery selection.
1313    @param  command  (string) the command to run (optional, default 'attach')
1314    @param  options  (object) the new settings to use for these SVG instances
1315    @return jQuery (object) for chaining further calls */
1316 $.fn.svg = function(options) {
1317         var otherArgs = Array.prototype.slice.call(arguments, 1);
1318         if (typeof options == 'string' && options == 'get') {
1319                 return $.svg['_' + options + 'SVG'].apply($.svg, [this[0]].concat(otherArgs));
1320         }
1321         return this.each(function() {
1322                 if (typeof options == 'string') {
1323                         $.svg['_' + options + 'SVG'].apply($.svg, [this].concat(otherArgs));
1324                 }
1325                 else {
1326                         $.svg._attachSVG(this, options || {});
1327                 } 
1328         });
1331 /* Determine whether an object is an array. */
1332 function isArray(a) {
1333         return (a && a.constructor == Array);
1336 // Singleton primary SVG interface
1337 $.svg = new SVGManager();
1339 })(jQuery);