remove other characters
[g.raphael] / seed / toSVG.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 importz = imports['seed/importz.js'].importz;
10 Raphael = importz('Raphael');
11 Roo = importz('Roo');
12
13 /**
14  * Escapes string for XML interpolation
15  * @param value string or number value to escape
16  * @returns string escaped
17  */
18 function escapeXML(s) {
19     if (typeof s === 'number')
20         return s.toString();
21
22     var replace = {
23         '&': 'amp',
24         '<': 'lt',
25         '>': 'gt',
26         '"': 'quot',
27         '\'': 'apos'
28     };
29
30     for (var entity in replace) {
31         s = s.replace(new RegExp(entity, 'g'), '&' + replace[entity] + ';');
32     }
33
34     return s;
35 }
36
37 /**
38  * Generic map function
39  * @param iterable the array or object to be mapped
40  * @param callback the callback function(element, key)
41  * @returns array
42  */
43 function map(iterable, callback) {
44     var mapped = new Array;
45
46     for (var i in iterable) {
47         if (iterable.hasOwnProperty(i)) {
48             var value = callback.call(this, iterable[i], i);
49
50             if (value !== null)
51                 mapped.push(value);
52         }
53     }
54
55     return mapped;
56 }
57
58 /**
59  * Generic reduce function
60  * @param iterable array or object to be reduced
61  * @param callback the callback function(initial, element, i)
62  * @param initial the initial value
63  * @return the reduced value
64  */
65 function reduce(iterable, callback, initial) {
66     for (var i in iterable) {
67         if (iterable.hasOwnProperty(i)) {
68             initial = callback.call(this, initial, iterable[i], i);
69         }
70     }
71
72     return initial;
73 }
74
75 /**
76  * Utility method for creating a tag
77  * @param name the tag name, e.g., 'text'
78  * @param attrs the attribute string, e.g., name1="val1" name2="val2"
79  * or attribute map, e.g., { name1 : 'val1', name2 : 'val2' }
80  * @param content the content string inside the tag
81  * @returns string of the tag
82  */
83 function tag(name, attrs, matrix, content) {
84     if (typeof content === 'undefined' || content === null) {
85         content = '';
86     }
87
88     if (typeof attrs === 'object') {
89         attrs = map(attrs, function (element, name) {
90             if (name === 'transform')
91                 return;
92
93             return name + '="' + escapeXML(element) + '"';
94         }).join(' ');
95     }
96
97     return '<' + name + (matrix ? ' transform="matrix(' + matrix.toString().replace(/^matrix\(|\)$/g, '') + ')" ' : ' ') + attrs + '>' +
98             content +
99             '</' + name + '>' + "\n";
100 }
101
102 /**
103  * @return object the style object
104  */
105 function extractStyle(node) {
106     //Roo.log(JSON.stringify(style));
107     return {
108         font: {
109             family: typeof node.attrs['font-family'] === 'undefined' ? null : node.attrs['font-family'],
110             size: typeof node.attrs['font-size'] === 'undefined' ? null : (node.attrs['font-size']),
111             anchor: typeof node.attrs['text-anchor'] === 'undefined' ? null : node.attrs['text-anchor'],
112         }
113     };
114 }
115
116 /**
117  * @param style object from style()
118  * @return string
119  */
120 function styleToString(style) {
121     // TODO figure out what is 'normal'
122     //Roo.log(JSON.stringify(style));
123     var r = [
124         'font-family:' + style.font.family,
125         'font-weight:normal',
126         'font-style:normal',
127         'font-stretch:normal',
128         'font-variant:normal'
129     ];
130     if (style.font.size !== null) {
131         r.push('font-size: ' + style.font.size + 'px')
132     }
133
134     return r.join(';')
135
136 }
137
138 /**
139  * Computes tspan dy using font size. This formula was empircally determined
140  * using a best-fit line. Works well in both VML and SVG browsers.
141  * @param fontSize number
142  * @return number
143  */
144 function computeTSpanDy(fontSize, line, lines) {
145     if (fontSize === null)
146         fontSize = 10;
147
148     //return fontSize * 4.5 / 13
149     return fontSize * 4.5 / 13 * (line - .2 - lines / 2) * 4.5;
150 }
151
152 var serializer = {
153     'text': function (node) {
154         var style = extractStyle(node);
155             
156         var tags = new Array;
157
158         var content = [];
159
160         var textArray = node.attrs['text'].split('\n');
161
162         textArray.forEach(function(v,k)  {
163             content.push(tag('tspan',
164                 {
165                     x: node.attrs.x,
166                     dy: computeTSpanDy(style.font.size, k + 1, textArray.length)
167                 },
168                 null,
169                 escapeXML(v)
170             ))
171
172         });
173
174         tags.push(tag(
175             'text',
176             reduce(
177                 node.attrs,
178                 function (initial, value, name) {
179                     if (name !== 'text' && name !== 'w' && name !== 'h') {
180                         if (name === 'font-size')
181                             value = value + 'px';
182
183                         initial[name] = escapeXML(value.toString());
184                     }
185
186                     return initial;
187                 },
188                 {
189                     style: 'text-anchor: ' + (style.font.anchor ? (style.font.anchor + ';') : 'middle;') +
190                             styleToString(style) + ';'
191                 }
192             ),
193             node.matrix,
194             content.join("")
195         ));
196
197         return tags;
198     },
199     'path': function (node) {
200         var initial = (node.matrix.a === 1 && node.matrix.d === 1) ? {} : {'transform': node.matrix.toString()};
201
202
203
204         return tag(
205                 'path',
206                 reduce(
207                         node.attrs,
208                         function (initial, value, name) {
209                             if (name === 'path') {
210                                 name = 'd';
211                             }
212
213                             initial[name] = (typeof (value) == 'undefined') ? '' : value.toString();
214
215                             return initial;
216                         },
217                         {
218                             style: 'fill:' + Raphael.color(node.attrs.fill).hex + ';'
219                         }
220                 ),
221                 node.matrix
222                 );
223     }
224     // Other serializers should go here
225 };
226
227 function toSVG() {
228     var paper = this,
229         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 + '">';
230
231
232     for (var node = paper.bottom; node != null; node = node.next) {
233         //if ( node.node.style.display === 'none' ) continue;
234
235         var attrs = '';
236         
237         // Use serializer
238         if (typeof serializer[node.type] === 'function') {
239             svg += serializer[node.type](node);
240
241             continue;
242         }
243
244         switch (node.type) {
245             case 'image':
246                 attrs += ' preserveAspectRatio="none"';
247                 break;
248         }
249         //Roo.log(JSON.stringify(node, null,4));
250
251         for (i in node.attrs) {
252             var name = i;
253
254             var val = node.attrs[i].toString();
255             switch (i) {
256                 case 'src':
257                     name = 'xlink:href';
258
259                     break;
260                 case 'transform':
261                     name = '';
262                     break;
263
264                 case 'stroke':
265                 case 'fill':
266                     //s(JSON.stringify(node, null,4));
267                     val = typeof (node.node.attributes[i]) == 'undefined' ? val : node.node.attributes[i];
268
269                     val = Raphael.color.getRGB(val).hex;
270                     //Roo.log("fill: " + val);
271                     break;
272             }
273
274             if (name) {
275                 attrs += ' ' + name + '="' + escapeXML(val) + '"';
276             }
277         }
278
279         svg += '<' + node.type + ' transform="matrix(' + node.matrix.toString().replace(/^matrix\(|\)$/g, '') + ')"' + attrs + '></' + node.type + '>' + "\n";
280     }
281
282     svg += '</svg>';
283
284     
285     return svg.replace(/[\u00A0-\u2666]/g, function(c) {
286         return '&#'+c.charCodeAt(0) + ';';
287     });
288         
289     
290 };
291