From ffa8ee7cb47c70459e1e5c41c6121e163dd003c7 Mon Sep 17 00:00:00 2001 From: Alan Knowles Date: Mon, 23 Jul 2012 13:29:07 +0800 Subject: [PATCH] plugins/raphael.export.js --- plugins/raphael.export.js | 231 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/plugins/raphael.export.js b/plugins/raphael.export.js index e69de29..c56c819 100644 --- a/plugins/raphael.export.js +++ b/plugins/raphael.export.js @@ -0,0 +1,231 @@ +/** + * 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 + ''; + } + + /** + * @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 = '' + ; + + 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 + '>'; + } + + svg += ''; + + R.svg = restore.svg; + R.vml = restore.vml; + + return svg; + }; +})(window.Raphael); \ No newline at end of file -- 2.39.2