+/**
+ * Raphael.Export https://github.com/ElbertF/Raphael.Export
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+
+(function(R) {
+ /**
+ * Escapes string for XML interpolation
+ * @param value string or number value to escape
+ * @returns string escaped
+ */
+ function escapeXML(s) {
+ if ( typeof s === 'number' ) return s.toString();
+
+ var replace = { '&': 'amp', '<': 'lt', '>': 'gt', '"': 'quot', '\'': 'apos' };
+
+ for ( var entity in replace ) {
+ s = s.replace(new RegExp(entity, 'g'), '&' + replace[entity] + ';');
+ }
+
+ return s;
+ }
+
+ /**
+ * Generic map function
+ * @param iterable the array or object to be mapped
+ * @param callback the callback function(element, key)
+ * @returns array
+ */
+ function map(iterable, callback) {
+ var mapped = new Array;
+
+ for ( var i in iterable ) {
+ if ( iterable.hasOwnProperty(i) ) {
+ var value = callback.call(this, iterable[i], i);
+
+ if ( value !== null ) mapped.push(value);
+ }
+ }
+
+ return mapped;
+ }
+
+ /**
+ * Generic reduce function
+ * @param iterable array or object to be reduced
+ * @param callback the callback function(initial, element, i)
+ * @param initial the initial value
+ * @return the reduced value
+ */
+ function reduce(iterable, callback, initial) {
+ for ( var i in iterable ) {
+ if ( iterable.hasOwnProperty(i) ) {
+ initial = callback.call(this, initial, iterable[i], i);
+ }
+ }
+
+ return initial;
+ }
+
+ /**
+ * Utility method for creating a tag
+ * @param name the tag name, e.g., 'text'
+ * @param attrs the attribute string, e.g., name1="val1" name2="val2"
+ * or attribute map, e.g., { name1 : 'val1', name2 : 'val2' }
+ * @param content the content string inside the tag
+ * @returns string of the tag
+ */
+ function tag(name, attrs, matrix, content) {
+ if ( typeof content === 'undefined' || content === null ) {
+ content = '';
+ }
+
+ if ( typeof attrs === 'object' ) {
+ attrs = map(attrs, function(element, name) {
+ if ( name === 'transform') return;
+
+ return name + '="' + escapeXML(element) + '"';
+ }).join(' ');
+ }
+
+ return '<' + name + ( matrix ? ' transform="matrix(' + matrix.toString().replace(/^matrix\(|\)$/g, '') + ')" ' : ' ' ) + attrs + '>' + content + '</' + name + '>';
+ }
+
+ /**
+ * @return object the style object
+ */
+ function extractStyle(node) {
+ return {
+ font: {
+ family: node.attrs.font.replace(/^.*?"(\w+)".*$/, '$1'),
+ size: typeof node.attrs['font-size'] === 'undefined' ? null : node.attrs['font-size']
+ }
+ };
+ }
+
+ /**
+ * @param style object from style()
+ * @return string
+ */
+ function styleToString(style) {
+ // TODO figure out what is 'normal'
+ return 'font: normal normal normal 10px/normal ' + style.font.family + ( style.font.size === null ? '' : '; font-size: ' + style.font.size + 'px' );
+ }
+
+ /**
+ * Computes tspan dy using font size. This formula was empircally determined
+ * using a best-fit line. Works well in both VML and SVG browsers.
+ * @param fontSize number
+ * @return number
+ */
+ function computeTSpanDy(fontSize, line, lines) {
+ if ( fontSize === null ) fontSize = 10;
+
+ //return fontSize * 4.5 / 13
+ return fontSize * 4.5 / 13 * ( line - .2 - lines / 2 ) * 3.5;
+ }
+
+ var serializer = {
+ 'text': function(node) {
+ style = extractStyle(node);
+
+ var tags = new Array;
+
+ map(node.attrs['text'].split('\n'), function(text, iterable, line) {
+ line = line || 0;
+ tags.push(tag(
+ 'text',
+ reduce(
+ node.attrs,
+ function(initial, value, name) {
+ if ( name !== 'text' && name !== 'w' && name !== 'h' ) {
+ if ( name === 'font-size') value = value + 'px';
+
+ initial[name] = escapeXML(value.toString());
+ }
+
+ return initial;
+ },
+ { style: 'text-anchor: middle; ' + styleToString(style) + ';' }
+ ),
+ node.matrix,
+ tag('tspan', { dy: computeTSpanDy(style.font.size, line + 1, node.attrs['text'].split('\n').length) }, null, text)
+ ));
+ });
+
+ return tags;
+ },
+ 'path' : function(node) {
+ var initial = ( node.matrix.a === 1 && node.matrix.d === 1 ) ? {} : { 'transform' : node.matrix.toString() };
+
+ return tag(
+ 'path',
+ reduce(
+ node.attrs,
+ function(initial, value, name) {
+ if ( name === 'path' ) name = 'd';
+
+ initial[name] = value.toString();
+
+ return initial;
+ },
+ {}
+ ),
+ node.matrix
+ );
+ }
+ // Other serializers should go here
+ };
+
+ R.fn.toSVG = function() {
+ var
+ paper = this,
+ restore = { svg: R.svg, vml: R.vml },
+ svg = '<svg style="overflow: hidden; position: relative;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' + paper.width + '" version="1.1" height="' + paper.height + '">'
+ ;
+
+ R.svg = true;
+ R.vml = false;
+
+ for ( var node = paper.bottom; node != null; node = node.next ) {
+ if ( node.node.style.display === 'none' ) continue;
+
+ var attrs = '';
+
+ // Use serializer
+ if ( typeof serializer[node.type] === 'function' ) {
+ svg += serializer[node.type](node);
+
+ continue;
+ }
+
+ switch ( node.type ) {
+ case 'image':
+ attrs += ' preserveAspectRatio="none"';
+ break;
+ }
+
+ for ( i in node.attrs ) {
+ var name = i;
+
+ switch ( i ) {
+ case 'src':
+ name = 'xlink:href';
+
+ break;
+ case 'transform':
+ name = '';
+
+ break;
+ }
+
+ if ( name ) {
+ attrs += ' ' + name + '="' + escapeXML(node.attrs[i].toString()) + '"';
+ }
+ }
+
+ svg += '<' + node.type + ' transform="matrix(' + node.matrix.toString().replace(/^matrix\(|\)$/g, '') + ')"' + attrs + '></' + node.type + '>';
+ }
+
+ svg += '</svg>';
+
+ R.svg = restore.svg;
+ R.vml = restore.vml;
+
+ return svg;
+ };
+})(window.Raphael);
\ No newline at end of file