2 * Raphael.Export https://github.com/ElbertF/Raphael.Export
4 * Licensed under the MIT license:
5 * http://www.opensource.org/licenses/mit-license.php
11 * Escapes string for XML interpolation
12 * @param value string or number value to escape
13 * @returns string escaped
15 function escapeXML(s) {
16 if ( typeof s === 'number' ) return s.toString();
26 for ( var entity in replace ) {
27 s = s.replace(new RegExp(entity, 'g'), '&' + replace[entity] + ';');
34 * Generic map function
35 * @param iterable the array or object to be mapped
36 * @param callback the callback function(element, key)
39 function map(iterable, callback) {
40 var mapped = new Array;
42 for ( var i in iterable ) {
43 if ( iterable.hasOwnProperty(i) ) {
44 var value = callback.call(this, iterable[i], i);
46 if ( value !== null ) mapped.push(value);
54 * Generic reduce function
55 * @param iterable array or object to be reduced
56 * @param callback the callback function(initial, element, i)
57 * @param initial the initial value
58 * @return the reduced value
60 function reduce(iterable, callback, initial) {
61 for ( var i in iterable ) {
62 if ( iterable.hasOwnProperty(i) ) {
63 initial = callback.call(this, initial, iterable[i], i);
71 * Utility method for creating a tag
72 * @param name the tag name, e.g., 'text'
73 * @param attrs the attribute string, e.g., name1="val1" name2="val2"
74 * or attribute map, e.g., { name1 : 'val1', name2 : 'val2' }
75 * @param content the content string inside the tag
76 * @returns string of the tag
78 function tag(name, attrs, matrix, content) {
79 if ( typeof content === 'undefined' || content === null ) {
83 if ( typeof attrs === 'object' ) {
84 attrs = map(attrs, function(element, name) {
85 if ( name === 'transform') return;
87 return name + '="' + escapeXML(element) + '"';
91 return '<' + name + ( matrix ? ' transform="matrix(' + matrix.toString().replace(/^matrix\(|\)$/g, '') + ')" ' : ' ' ) + attrs + '>' +
93 '</' + name + '>' + "\n";
97 * @return object the style object
99 function extractStyle(node) {
102 family: node.attrs.font.replace(/^.*?"(\w+)".*$/, '$1'),
103 size: typeof node.attrs['font-size'] === 'undefined' ? null : node.attrs['font-size'],
104 anchor : typeof node.attrs['text-anchor'] === 'undefined' ? null : node.attrs['text-anchor'],
110 * @param style object from style()
113 function styleToString(style) {
114 // TODO figure out what is 'normal'
117 'font-family:' + style.font.family,
118 'font-weight:normal',
120 'font-stretch:normal',
121 'font-variant:normal'
123 if (style.font.size !== null ) {
124 r.push('font-size: ' + style.font.size + 'px')
132 * Computes tspan dy using font size. This formula was empircally determined
133 * using a best-fit line. Works well in both VML and SVG browsers.
134 * @param fontSize number
137 function computeTSpanDy(fontSize, line, lines) {
138 if ( fontSize === null ) fontSize = 10;
140 //return fontSize * 4.5 / 13
141 return fontSize * 4.5 / 13 * ( line - .2 - lines / 2 ) * 3.5;
145 'text': function(node) {
146 style = extractStyle(node);
148 var tags = new Array;
150 map(node.attrs['text'].split('\n'), function(text, iterable, line) {
156 function(initial, value, name) {
157 if ( name !== 'text' && name !== 'w' && name !== 'h' ) {
158 if ( name === 'font-size') value = value + 'px';
160 initial[name] = escapeXML(value.toString());
166 style: 'text-anchor: ' + (style.font.anchor ? style.font.anchor : 'middle;') +
167 styleToString(style) + ';' }
172 dy: computeTSpanDy(style.font.size, line + 1, node.attrs['text'].split('\n').length)
182 'path' : function(node) {
183 var initial = ( node.matrix.a === 1 && node.matrix.d === 1 ) ? {} : { 'transform' : node.matrix.toString() };
189 function(initial, value, name) {
190 if ( name === 'path' ) name = 'd';
192 initial[name] = value.toString();
201 // Other serializers should go here
204 R.fn.toSVG = function() {
207 restore = { svg: R.svg, vml: R.vml },
208 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 + '">'
214 for ( var node = paper.bottom; node != null; node = node.next ) {
215 if ( node.node.style.display === 'none' ) continue;
220 if ( typeof serializer[node.type] === 'function' ) {
221 svg += serializer[node.type](node);
226 switch ( node.type ) {
228 attrs += ' preserveAspectRatio="none"';
232 for ( i in node.attrs ) {
247 attrs += ' ' + name + '="' + escapeXML(node.attrs[i].toString()) + '"';
251 svg += '<' + node.type + ' transform="matrix(' + node.matrix.toString().replace(/^matrix\(|\)$/g, '') + ')"' + attrs + '></' + node.type + '>';