plugins/raphael.export.js
[raphael] / plugins / raphael.export.js
1 /**
2  * Raphael.Export https://github.com/ElbertF/Raphael.Export
3  *
4  * Licensed under the MIT license:
5  * http://www.opensource.org/licenses/mit-license.php
6  *
7  */
8
9 (function(R) {
10         /**
11         * Escapes string for XML interpolation
12         * @param value string or number value to escape
13         * @returns string escaped
14         */
15         function escapeXML(s) {
16                 if ( typeof s === 'number' ) return s.toString();
17
18                 var replace = {
19             '&': 'amp',
20             '<': 'lt',
21             '>': 'gt',
22             '"': 'quot',
23             '\'': 'apos'
24         };
25
26                 for ( var entity in replace ) {
27                         s = s.replace(new RegExp(entity, 'g'), '&' + replace[entity] + ';');
28                 }
29
30                 return s;
31         }
32
33         /**
34         * Generic map function
35         * @param iterable the array or object to be mapped
36         * @param callback the callback function(element, key)
37         * @returns array
38         */
39         function map(iterable, callback) {
40                 var mapped = new Array;
41
42                 for ( var i in iterable ) {
43                         if ( iterable.hasOwnProperty(i) ) {
44                                 var value = callback.call(this, iterable[i], i);
45
46                                 if ( value !== null ) mapped.push(value);
47                         }
48                 }
49
50                 return mapped;
51         }
52
53         /**
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
59         */
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);
64                         }
65                 }
66
67                 return initial;
68         }
69
70         /**
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
77         */
78         function tag(name, attrs, matrix, content) {
79                 if ( typeof content === 'undefined' || content === null ) {
80                         content = '';
81                 }
82
83                 if ( typeof attrs === 'object' ) {
84                         attrs = map(attrs, function(element, name) {
85                                 if ( name === 'transform') return;
86
87                                 return name + '="' + escapeXML(element) + '"';
88                         }).join(' ');
89                 }
90
91                 return '<' + name + ( matrix ? ' transform="matrix(' + matrix.toString().replace(/^matrix\(|\)$/g, '') + ')" ' : ' ' ) + attrs + '>' +
92              content +
93             '</' + name + '>' + "\n";
94         }
95
96         /**
97         * @return object the style object
98         */
99         function extractStyle(node) {
100                 return {
101                         font: {
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'],
105                                 }
106                         };
107         }
108
109         /**
110         * @param style object from style()
111         * @return string
112         */
113         function styleToString(style) {
114                 // TODO figure out what is 'normal'
115         
116         var r = [
117                 'font-family:' + style.font.family,
118                 'font-weight:normal',
119                 'font-style:normal',
120                 'font-stretch:normal',
121                 'font-variant:normal'
122         ];
123         if (style.font.size !== null ) {
124                 r.push('font-size: ' + style.font.size + 'px') 
125         }
126         
127         return r.join(';')
128         
129         }
130
131         /**
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
135         * @return number
136         */
137         function computeTSpanDy(fontSize, line, lines) {
138                 if ( fontSize === null ) fontSize = 10;
139
140                 //return fontSize * 4.5 / 13
141                 return fontSize * 4.5 / 13 * ( line - .2 - lines / 2 ) * 3.5;
142         }
143
144         var serializer = {
145                 'text': function(node) {
146                         style = extractStyle(node);
147             Roo.log(style);
148                         var tags = new Array;
149
150                         map(node.attrs['text'].split('\n'), function(text, iterable, line) {
151                 line = line || 0;
152                                 tags.push(tag(
153                                         'text',
154                                         reduce(
155                                                 node.attrs,
156                                                 function(initial, value, name) {
157                                                         if ( name !== 'text' && name !== 'w' && name !== 'h' ) {
158                                                                 if ( name === 'font-size') value = value + 'px';
159
160                                                                 initial[name] = escapeXML(value.toString());
161                                                         }
162
163                                                         return initial;
164                                                 },
165                                                 {
166                                 style: 'text-anchor: ' + (style.font.anchor ? (style.font.anchor  +';' ): 'middle;') + 
167                                     styleToString(style) + ';'
168                         }
169                                         ),
170                                         node.matrix,
171                                         tag('tspan',
172                             {
173                                 dy: computeTSpanDy(style.font.size, line + 1, node.attrs['text'].split('\n').length)
174                             },
175                             null,
176                             escapeXML(text)
177                     )
178                                 ));
179                         });
180
181                         return tags;
182                 },
183                 'path' : function(node) {
184                         var initial = ( node.matrix.a === 1 && node.matrix.d === 1 ) ? {} : { 'transform' : node.matrix.toString() };
185
186                         return tag(
187                                 'path',
188                                 reduce(
189                                         node.attrs,
190                                         function(initial, value, name) {
191                                                 if ( name === 'path' ) {
192                             name = 'd';
193                         }
194
195                                                 initial[name] = (typeof(value) == 'undefined') ? '' : value.toString();
196
197                                                 return initial;
198                                         },
199                     {
200                             style:  node.attrs.style
201                     }
202                                 ),
203                                 node.matrix
204                                 );
205                 }
206                 // Other serializers should go here
207         };
208
209         R.fn.toSVG = function() {
210                 var
211                         paper   = this,
212                         restore = { svg: R.svg, vml: R.vml },
213                         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                         ;
215
216                 R.svg = true;
217                 R.vml = false;
218
219                 for ( var node = paper.bottom; node != null; node = node.next ) {
220                         if ( node.node.style.display === 'none' ) continue;
221
222                         var attrs = '';
223
224                         // Use serializer
225                         if ( typeof serializer[node.type] === 'function' ) {
226                                 svg += serializer[node.type](node);
227
228                                 continue;
229                         }
230
231                         switch ( node.type ) {
232                                 case 'image':
233                                         attrs += ' preserveAspectRatio="none"';
234                                         break;
235                         }
236
237                         for ( i in node.attrs ) {
238                                 var name = i;
239                 var val = node.attrs[i].toString();
240                                 switch ( i ) {
241                                         case 'src':
242                                                 name = 'xlink:href';
243
244                                                 break;
245                                         case 'transform':
246                                                 name = '';
247                                                 break;
248                     
249                     case 'stroke':
250                     case 'fill':
251                         val = Raphael.getRGB(val).hex;
252                         break;
253                                 }
254
255                                 if ( name ) {
256                                         attrs += ' ' + name + '="' + escapeXML(val) + '"';
257                                 }
258                         }
259
260                         svg += '<' + node.type + ' transform="matrix(' + node.matrix.toString().replace(/^matrix\(|\)$/g, '') + ')"' + attrs + '></' + node.type + '>';
261                 }
262
263                 svg += '</svg>';
264
265                 R.svg = restore.svg;
266                 R.vml = restore.vml;
267
268                 return svg;
269         };
270 })(window.Raphael);