1 | /* $Id$ $Revision$ */ |
2 | /* vim:set shiftwidth=4 ts=8: */ |
3 | |
4 | /************************************************************************* |
5 | * Copyright (c) 2011 AT&T Intellectual Property |
6 | * All rights reserved. This program and the accompanying materials |
7 | * are made available under the terms of the Eclipse Public License v1.0 |
8 | * which accompanies this distribution, and is available at |
9 | * http://www.eclipse.org/legal/epl-v10.html |
10 | * |
11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
12 | *************************************************************************/ |
13 | |
14 | /* Comments on the SVG coordinate system (SN 8 Dec 2006): |
15 | The initial <svg> element defines the SVG coordinate system so |
16 | that the graphviz canvas (in units of points) fits the intended |
17 | absolute size in inches. After this, the situation should be |
18 | that "px" = "pt" in SVG, so we can dispense with stating units. |
19 | Also, the input units (such as fontsize) should be preserved |
20 | without scaling in the output SVG (as long as the graph size |
21 | was not constrained.) |
22 | */ |
23 | |
24 | #include "config.h" |
25 | |
26 | #include <stdarg.h> |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | #include <ctype.h> |
30 | |
31 | #include "macros.h" |
32 | #include "const.h" |
33 | |
34 | #include "gvplugin_render.h" |
35 | #include "agxbuf.h" |
36 | #include "utils.h" |
37 | #include "gvplugin_device.h" |
38 | #include "gvio.h" |
39 | #include "gvcint.h" |
40 | |
41 | typedef enum { FORMAT_SVG, FORMAT_SVGZ, } format_type; |
42 | |
43 | /* SVG dash array */ |
44 | static char *sdasharray = "5,2" ; |
45 | /* SVG dot array */ |
46 | static char *sdotarray = "1,5" ; |
47 | |
48 | #ifndef HAVE_STRCASECMP |
49 | extern int strcasecmp(const char *s1, const char *s2); |
50 | #endif |
51 | |
52 | static void svg_bzptarray(GVJ_t * job, pointf * A, int n) |
53 | { |
54 | int i; |
55 | char c; |
56 | |
57 | c = 'M'; /* first point */ |
58 | #if EDGEALIGN |
59 | if (A[0].x <= A[n-1].x) { |
60 | #endif |
61 | for (i = 0; i < n; i++) { |
62 | gvprintf(job, "%c" , c); |
63 | gvprintdouble(job, A[i].x); |
64 | gvputs(job, "," ); |
65 | gvprintdouble(job, -A[i].y); |
66 | if (i == 0) |
67 | c = 'C'; /* second point */ |
68 | else |
69 | c = ' '; /* remaining points */ |
70 | } |
71 | #if EDGEALIGN |
72 | } else { |
73 | for (i = n-1; i >= 0; i--) { |
74 | gvprintf(job, "%c" , c); |
75 | gvprintdouble(job, A[i].x); |
76 | gvputs(job, "," ); |
77 | gvprintdouble(job, -A[i].y); |
78 | if (i == 0) |
79 | c = 'C'; /* second point */ |
80 | else |
81 | c = ' '; /* remaining points */ |
82 | } |
83 | } |
84 | #endif |
85 | } |
86 | |
87 | static void svg_print_id_class(GVJ_t * job, char* id, char* idx, char* kind, void* obj) |
88 | { |
89 | char* str; |
90 | |
91 | gvputs(job, "<g id=\"" ); |
92 | gvputs(job, xml_string(id)); |
93 | if (idx) |
94 | gvprintf (job, "_%s" , xml_string(idx)); |
95 | gvprintf(job, "\" class=\"%s" , kind); |
96 | if ((str = agget(obj, "class" )) && *str) { |
97 | gvputs(job, " " ); |
98 | gvputs(job, xml_string(str)); |
99 | } |
100 | gvputs(job, "\"" ); |
101 | } |
102 | |
103 | static void svg_print_color(GVJ_t * job, gvcolor_t color) |
104 | { |
105 | switch (color.type) { |
106 | case COLOR_STRING: |
107 | gvputs(job, color.u.string); |
108 | break; |
109 | case RGBA_BYTE: |
110 | if (color.u.rgba[3] == 0) /* transparent */ |
111 | gvputs(job, "transparent" ); |
112 | else |
113 | gvprintf(job, "#%02x%02x%02x" , |
114 | color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]); |
115 | break; |
116 | default: |
117 | assert(0); /* internal error */ |
118 | } |
119 | } |
120 | |
121 | static void svg_grstyle(GVJ_t * job, int filled, int gid) |
122 | { |
123 | obj_state_t *obj = job->obj; |
124 | |
125 | gvputs(job, " fill=\"" ); |
126 | if (filled == GRADIENT) { |
127 | gvprintf(job, "url(#l_%d)" , gid); |
128 | } else if (filled == RGRADIENT) { |
129 | gvprintf(job, "url(#r_%d)" , gid); |
130 | } else if (filled) { |
131 | svg_print_color(job, obj->fillcolor); |
132 | if (obj->fillcolor.type == RGBA_BYTE |
133 | && obj->fillcolor.u.rgba[3] > 0 |
134 | && obj->fillcolor.u.rgba[3] < 255) |
135 | gvprintf(job, "\" fill-opacity=\"%f" , |
136 | ((float) obj->fillcolor.u.rgba[3] / 255.0)); |
137 | } else { |
138 | gvputs(job, "none" ); |
139 | } |
140 | gvputs(job, "\" stroke=\"" ); |
141 | svg_print_color(job, obj->pencolor); |
142 | if (obj->penwidth != PENWIDTH_NORMAL) { |
143 | gvputs(job, "\" stroke-width=\"" ); |
144 | gvprintdouble(job, obj->penwidth); |
145 | } |
146 | if (obj->pen == PEN_DASHED) { |
147 | gvprintf(job, "\" stroke-dasharray=\"%s" , sdasharray); |
148 | } else if (obj->pen == PEN_DOTTED) { |
149 | gvprintf(job, "\" stroke-dasharray=\"%s" , sdotarray); |
150 | } |
151 | if (obj->pencolor.type == RGBA_BYTE && obj->pencolor.u.rgba[3] > 0 |
152 | && obj->pencolor.u.rgba[3] < 255) |
153 | gvprintf(job, "\" stroke-opacity=\"%f" , |
154 | ((float) obj->pencolor.u.rgba[3] / 255.0)); |
155 | |
156 | gvputs(job, "\"" ); |
157 | } |
158 | |
159 | static void (GVJ_t * job, char *str) |
160 | { |
161 | gvputs(job, "<!-- " ); |
162 | gvputs(job, xml_string(str)); |
163 | gvputs(job, " -->\n" ); |
164 | } |
165 | |
166 | static void svg_begin_job(GVJ_t * job) |
167 | { |
168 | char *s; |
169 | gvputs(job, |
170 | "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" ); |
171 | if ((s = agget(job->gvc->g, "stylesheet" )) && s[0]) { |
172 | gvputs(job, "<?xml-stylesheet href=\"" ); |
173 | gvputs(job, s); |
174 | gvputs(job, "\" type=\"text/css\"?>\n" ); |
175 | } |
176 | #if 0 |
177 | gvputs(job, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" ); |
178 | gvputs(job, |
179 | " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"" ); |
180 | /* This is to work around a bug in the SVG 1.0 DTD */ |
181 | gvputs(job, |
182 | " [\n <!ATTLIST svg xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\">\n]" ); |
183 | #else |
184 | gvputs(job, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n" ); |
185 | gvputs(job, |
186 | " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" ); |
187 | #endif |
188 | |
189 | gvputs(job, "<!-- Generated by " ); |
190 | gvputs(job, xml_string(job->common->info[0])); |
191 | gvputs(job, " version " ); |
192 | gvputs(job, xml_string(job->common->info[1])); |
193 | gvputs(job, " (" ); |
194 | gvputs(job, xml_string(job->common->info[2])); |
195 | gvputs(job, ")\n" ); |
196 | gvputs(job, " -->\n" ); |
197 | } |
198 | |
199 | static void svg_begin_graph(GVJ_t * job) |
200 | { |
201 | obj_state_t *obj = job->obj; |
202 | |
203 | gvputs(job, "<!--" ); |
204 | if (agnameof(obj->u.g)[0]) { |
205 | gvputs(job, " Title: " ); |
206 | gvputs(job, xml_string(agnameof(obj->u.g))); |
207 | } |
208 | gvprintf(job, " Pages: %d -->\n" , |
209 | job->pagesArraySize.x * job->pagesArraySize.y); |
210 | |
211 | gvprintf(job, "<svg width=\"%dpt\" height=\"%dpt\"\n" , |
212 | job->width, job->height); |
213 | gvprintf(job, " viewBox=\"%.2f %.2f %.2f %.2f\"" , |
214 | job->canvasBox.LL.x, |
215 | job->canvasBox.LL.y, |
216 | job->canvasBox.UR.x, |
217 | job->canvasBox.UR.y); |
218 | /* namespace of svg */ |
219 | gvputs(job, " xmlns=\"http://www.w3.org/2000/svg\"" ); |
220 | /* namespace of xlink */ |
221 | gvputs(job, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" ); |
222 | gvputs(job, ">\n" ); |
223 | } |
224 | |
225 | static void svg_end_graph(GVJ_t * job) |
226 | { |
227 | gvputs(job, "</svg>\n" ); |
228 | } |
229 | |
230 | static void svg_begin_layer(GVJ_t * job, char *layername, int layerNum, |
231 | int numLayers) |
232 | { |
233 | obj_state_t *obj = job->obj; |
234 | |
235 | svg_print_id_class(job, layername, NULL, "layer" , obj->u.g); |
236 | gvputs(job, ">\n" ); |
237 | } |
238 | |
239 | static void svg_end_layer(GVJ_t * job) |
240 | { |
241 | gvputs(job, "</g>\n" ); |
242 | } |
243 | |
244 | /* svg_begin_page: |
245 | * Currently, svg output does not support pages. |
246 | * FIX: If implemented, we must guarantee the id is unique. |
247 | */ |
248 | static void svg_begin_page(GVJ_t * job) |
249 | { |
250 | obj_state_t *obj = job->obj; |
251 | |
252 | /* its really just a page of the graph, but its still a graph, |
253 | * and it is the entire graph if we're not currently paging */ |
254 | svg_print_id_class(job, obj->id, NULL, "graph" , obj->u.g); |
255 | gvputs(job, " transform=\"scale(" ); |
256 | gvprintdouble(job, job->scale.x); |
257 | gvputs(job, " " ); |
258 | gvprintdouble(job, job->scale.y); |
259 | gvprintf(job, ") rotate(%d) translate(" , -job->rotation); |
260 | gvprintdouble(job, job->translation.x); |
261 | gvputs(job, " " ); |
262 | gvprintdouble(job, -job->translation.y); |
263 | gvputs(job, ")\">\n" ); |
264 | /* default style */ |
265 | if (agnameof(obj->u.g)[0]) { |
266 | gvputs(job, "<title>" ); |
267 | gvputs(job, xml_string(agnameof(obj->u.g))); |
268 | gvputs(job, "</title>\n" ); |
269 | } |
270 | } |
271 | |
272 | static void svg_end_page(GVJ_t * job) |
273 | { |
274 | gvputs(job, "</g>\n" ); |
275 | } |
276 | |
277 | static void svg_begin_cluster(GVJ_t * job) |
278 | { |
279 | obj_state_t *obj = job->obj; |
280 | |
281 | svg_print_id_class(job, obj->id, NULL, "cluster" , obj->u.sg); |
282 | gvputs(job, ">\n" ); |
283 | gvputs(job, "<title>" ); |
284 | gvputs(job, xml_string(agnameof(obj->u.g))); |
285 | gvputs(job, "</title>\n" ); |
286 | } |
287 | |
288 | static void svg_end_cluster(GVJ_t * job) |
289 | { |
290 | gvputs(job, "</g>\n" ); |
291 | } |
292 | |
293 | static void svg_begin_node(GVJ_t * job) |
294 | { |
295 | obj_state_t *obj = job->obj; |
296 | char* idx; |
297 | |
298 | if (job->layerNum > 1) |
299 | idx = job->gvc->layerIDs[job->layerNum]; |
300 | else |
301 | idx = NULL; |
302 | svg_print_id_class(job, obj->id, idx, "node" , obj->u.n); |
303 | gvputs(job, ">\n" ); |
304 | gvputs(job, "<title>" ); |
305 | gvputs(job, xml_string(agnameof(obj->u.n))); |
306 | gvputs(job, "</title>\n" ); |
307 | } |
308 | |
309 | static void svg_end_node(GVJ_t * job) |
310 | { |
311 | gvputs(job, "</g>\n" ); |
312 | } |
313 | |
314 | static void svg_begin_edge(GVJ_t * job) |
315 | { |
316 | obj_state_t *obj = job->obj; |
317 | char *ename; |
318 | |
319 | svg_print_id_class(job, obj->id, NULL, "edge" , obj->u.e); |
320 | gvputs(job, ">\n" ); |
321 | |
322 | gvputs(job, "<title>" ); |
323 | ename = strdup_and_subst_obj("\\E" , (void *) (obj->u.e)); |
324 | gvputs(job, xml_string(ename)); |
325 | free(ename); |
326 | gvputs(job, "</title>\n" ); |
327 | } |
328 | |
329 | static void svg_end_edge(GVJ_t * job) |
330 | { |
331 | gvputs(job, "</g>\n" ); |
332 | } |
333 | |
334 | static void |
335 | svg_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target, |
336 | char *id) |
337 | { |
338 | gvputs(job, "<g" ); |
339 | if (id) { |
340 | gvputs(job, " id=\"a_" ); |
341 | gvputs(job, xml_string(id)); |
342 | gvputs(job, "\"" ); |
343 | } |
344 | gvputs(job, ">" ); |
345 | |
346 | gvputs(job, "<a" ); |
347 | #if 0 |
348 | /* the svg spec implies this can be omitted: http://www.w3.org/TR/SVG/linking.html#Links */ |
349 | gvputs(job, " xlink:type=\"simple\"" ); |
350 | #endif |
351 | if (href && href[0]) { |
352 | gvputs(job, " xlink:href=\"" ); |
353 | gvputs(job, href); |
354 | gvputs(job, "\"" ); |
355 | } |
356 | #if 0 |
357 | /* linking to itself, just so that it can have a xlink:link in the anchor, seems wrong. |
358 | * it changes the behavior in browsers, the link apears in the bottom information bar |
359 | */ |
360 | else { |
361 | assert(id && id[0]); /* there should always be an id available */ |
362 | gvputs(job, " xlink:href=\"#" ); |
363 | gvputs(job, xml_url_string(href)); |
364 | gvputs(job, "\"" ); |
365 | } |
366 | #endif |
367 | if (tooltip && tooltip[0]) { |
368 | gvputs(job, " xlink:title=\"" ); |
369 | gvputs(job, xml_string0(tooltip, 1)); |
370 | gvputs(job, "\"" ); |
371 | } |
372 | if (target && target[0]) { |
373 | gvputs(job, " target=\"" ); |
374 | gvputs(job, xml_string(target)); |
375 | gvputs(job, "\"" ); |
376 | } |
377 | gvputs(job, ">\n" ); |
378 | } |
379 | |
380 | static void svg_end_anchor(GVJ_t * job) |
381 | { |
382 | gvputs(job, "</a>\n" ); |
383 | gvputs(job, "</g>\n" ); |
384 | } |
385 | |
386 | static void svg_textspan(GVJ_t * job, pointf p, textspan_t * span) |
387 | { |
388 | obj_state_t *obj = job->obj; |
389 | PostscriptAlias *pA; |
390 | char *family = NULL, *weight = NULL, *stretch = NULL, *style = NULL; |
391 | unsigned int flags; |
392 | |
393 | gvputs(job, "<text" ); |
394 | switch (span->just) { |
395 | case 'l': |
396 | gvputs(job, " text-anchor=\"start\"" ); |
397 | break; |
398 | case 'r': |
399 | gvputs(job, " text-anchor=\"end\"" ); |
400 | break; |
401 | default: |
402 | case 'n': |
403 | gvputs(job, " text-anchor=\"middle\"" ); |
404 | break; |
405 | } |
406 | p.y += span->yoffset_centerline; |
407 | if (!obj->labeledgealigned) { |
408 | gvputs(job, " x=\"" ); |
409 | gvprintdouble(job, p.x); |
410 | gvputs(job, "\" y=\"" ); |
411 | gvprintdouble(job, -p.y); |
412 | gvputs(job, "\"" ); |
413 | } |
414 | pA = span->font->postscript_alias; |
415 | if (pA) { |
416 | switch (GD_fontnames(job->gvc->g)) { |
417 | case PSFONTS: |
418 | family = pA->name; |
419 | weight = pA->weight; |
420 | style = pA->style; |
421 | break; |
422 | case SVGFONTS: |
423 | family = pA->svg_font_family; |
424 | weight = pA->svg_font_weight; |
425 | style = pA->svg_font_style; |
426 | break; |
427 | default: |
428 | case NATIVEFONTS: |
429 | family = pA->family; |
430 | weight = pA->weight; |
431 | style = pA->style; |
432 | break; |
433 | } |
434 | stretch = pA->stretch; |
435 | |
436 | gvprintf(job, " font-family=\"%s" , family); |
437 | if (pA->svg_font_family) |
438 | gvprintf(job, ",%s" , pA->svg_font_family); |
439 | gvputs(job, "\"" ); |
440 | if (weight) |
441 | gvprintf(job, " font-weight=\"%s\"" , weight); |
442 | if (stretch) |
443 | gvprintf(job, " font-stretch=\"%s\"" , stretch); |
444 | if (style) |
445 | gvprintf(job, " font-style=\"%s\"" , style); |
446 | } else |
447 | gvprintf(job, " font-family=\"%s\"" , span->font->name); |
448 | if ((span->font) && (flags = span->font->flags)) { |
449 | if ((flags & HTML_BF) && !weight) |
450 | gvprintf(job, " font-weight=\"bold\"" ); |
451 | if ((flags & HTML_IF) && !style) |
452 | gvprintf(job, " font-style=\"italic\"" ); |
453 | if ((flags & (HTML_UL|HTML_S|HTML_OL))) { |
454 | int comma = 0; |
455 | gvprintf(job, " text-decoration=\"" ); |
456 | if ((flags & HTML_UL)) { |
457 | gvprintf(job, "underline" ); |
458 | comma = 1; |
459 | } |
460 | if ((flags & HTML_OL)) { |
461 | gvprintf(job, "%soverline" , (comma?"," :"" )); |
462 | comma = 1; |
463 | } |
464 | if ((flags & HTML_S)) |
465 | gvprintf(job, "%sline-through" , (comma?"," :"" )); |
466 | gvprintf(job, "\"" ); |
467 | } |
468 | if ((flags & HTML_SUP)) |
469 | gvprintf(job, " baseline-shift=\"super\"" ); |
470 | if ((flags & HTML_SUB)) |
471 | gvprintf(job, " baseline-shift=\"sub\"" ); |
472 | } |
473 | |
474 | gvprintf(job, " font-size=\"%.2f\"" , span->font->size); |
475 | switch (obj->pencolor.type) { |
476 | case COLOR_STRING: |
477 | if (strcasecmp(obj->pencolor.u.string, "black" )) |
478 | gvprintf(job, " fill=\"%s\"" , obj->pencolor.u.string); |
479 | break; |
480 | case RGBA_BYTE: |
481 | gvprintf(job, " fill=\"#%02x%02x%02x\"" , |
482 | obj->pencolor.u.rgba[0], obj->pencolor.u.rgba[1], |
483 | obj->pencolor.u.rgba[2]); |
484 | if (obj->pencolor.u.rgba[3] > 0 && obj->pencolor.u.rgba[3] < 255) |
485 | gvprintf(job, " fill-opacity=\"%f\"" , ((float) obj->pencolor.u.rgba[3] / 255.0)); |
486 | break; |
487 | default: |
488 | assert(0); /* internal error */ |
489 | } |
490 | gvputs(job, ">" ); |
491 | if (obj->labeledgealigned) { |
492 | gvprintf(job, "<textPath xlink:href=\"#%s_p\" startOffset=\"50%%\">" , xml_string(obj->id)); |
493 | gvputs(job, "<tspan x=\"0\" dy=\"" ); |
494 | gvprintdouble(job, -p.y); |
495 | gvputs(job, "\">" ); |
496 | } |
497 | gvputs(job, xml_string0(span->str, TRUE)); |
498 | if (obj->labeledgealigned) |
499 | gvprintf (job, "</tspan></textPath>" ); |
500 | gvputs(job, "</text>\n" ); |
501 | } |
502 | |
503 | /* svg_gradstyle |
504 | * Outputs the SVG statements that define the gradient pattern |
505 | */ |
506 | static int svg_gradstyle(GVJ_t * job, pointf * A, int n) |
507 | { |
508 | pointf G[2]; |
509 | float angle; |
510 | static int gradId; |
511 | int id = gradId++; |
512 | |
513 | obj_state_t *obj = job->obj; |
514 | angle = obj->gradient_angle * M_PI / 180; //angle of gradient line |
515 | G[0].x = G[0].y = G[1].x = G[1].y = 0.; |
516 | get_gradient_points(A, G, n, angle, 0); //get points on gradient line |
517 | |
518 | gvprintf(job, |
519 | "<defs>\n<linearGradient id=\"l_%d\" gradientUnits=\"userSpaceOnUse\" " , id); |
520 | gvputs(job, "x1=\"" ); |
521 | gvprintdouble(job, G[0].x); |
522 | gvputs(job, "\" y1=\"" ); |
523 | gvprintdouble(job, G[0].y); |
524 | gvputs(job, "\" x2=\"" ); |
525 | gvprintdouble(job, G[1].x); |
526 | gvputs(job, "\" y2=\"" ); |
527 | gvprintdouble(job, G[1].y); |
528 | gvputs(job, "\" >\n" ); |
529 | if (obj->gradient_frac > 0) |
530 | gvprintf(job, "<stop offset=\"%.03f\" style=\"stop-color:" , obj->gradient_frac - 0.001); |
531 | else |
532 | gvputs(job, "<stop offset=\"0\" style=\"stop-color:" ); |
533 | svg_print_color(job, obj->fillcolor); |
534 | gvputs(job, ";stop-opacity:" ); |
535 | if (obj->fillcolor.type == RGBA_BYTE && obj->fillcolor.u.rgba[3] > 0 |
536 | && obj->fillcolor.u.rgba[3] < 255) |
537 | gvprintf(job, "%f" , ((float) obj->fillcolor.u.rgba[3] / 255.0)); |
538 | else |
539 | gvputs(job, "1." ); |
540 | gvputs(job, ";\"/>\n" ); |
541 | if (obj->gradient_frac > 0) |
542 | gvprintf(job, "<stop offset=\"%.03f\" style=\"stop-color:" , obj->gradient_frac); |
543 | else |
544 | gvputs(job, "<stop offset=\"1\" style=\"stop-color:" ); |
545 | svg_print_color(job, obj->stopcolor); |
546 | gvputs(job, ";stop-opacity:" ); |
547 | if (obj->stopcolor.type == RGBA_BYTE && obj->stopcolor.u.rgba[3] > 0 |
548 | && obj->stopcolor.u.rgba[3] < 255) |
549 | gvprintf(job, "%f" , ((float) obj->stopcolor.u.rgba[3] / 255.0)); |
550 | else |
551 | gvputs(job, "1." ); |
552 | gvputs(job, ";\"/>\n</linearGradient>\n</defs>\n" ); |
553 | return id; |
554 | } |
555 | |
556 | /* svg_rgradstyle |
557 | * Outputs the SVG statements that define the radial gradient pattern |
558 | */ |
559 | static int svg_rgradstyle(GVJ_t * job, pointf * A, int n) |
560 | { |
561 | /* pointf G[2]; */ |
562 | float angle; |
563 | int ifx, ify; |
564 | static int rgradId; |
565 | int id = rgradId++; |
566 | |
567 | obj_state_t *obj = job->obj; |
568 | angle = obj->gradient_angle * M_PI / 180; //angle of gradient line |
569 | /* G[0].x = G[0].y = G[1].x = G[1].y; */ |
570 | /* get_gradient_points(A, G, n, 0, 1); */ |
571 | if (angle == 0.) { |
572 | ifx = ify = 50; |
573 | } else { |
574 | ifx = 50 * (1 + cos(angle)); |
575 | ify = 50 * (1 - sin(angle)); |
576 | } |
577 | gvprintf(job, |
578 | "<defs>\n<radialGradient id=\"r_%d\" cx=\"50%%\" cy=\"50%%\" r=\"75%%\" fx=\"%d%%\" fy=\"%d%%\">\n" , |
579 | id, ifx, ify); |
580 | gvputs(job, "<stop offset=\"0\" style=\"stop-color:" ); |
581 | svg_print_color(job, obj->fillcolor); |
582 | gvputs(job, ";stop-opacity:" ); |
583 | if (obj->fillcolor.type == RGBA_BYTE && obj->fillcolor.u.rgba[3] > 0 |
584 | && obj->fillcolor.u.rgba[3] < 255) |
585 | gvprintf(job, "%f" , ((float) obj->fillcolor.u.rgba[3] / 255.0)); |
586 | else |
587 | gvputs(job, "1." ); |
588 | gvputs(job, ";\"/>\n" ); |
589 | gvputs(job, "<stop offset=\"1\" style=\"stop-color:" ); |
590 | svg_print_color(job, obj->stopcolor); |
591 | gvputs(job, ";stop-opacity:" ); |
592 | if (obj->stopcolor.type == RGBA_BYTE && obj->stopcolor.u.rgba[3] > 0 |
593 | && obj->stopcolor.u.rgba[3] < 255) |
594 | gvprintf(job, "%f" , ((float) obj->stopcolor.u.rgba[3] / 255.0)); |
595 | else |
596 | gvputs(job, "1." ); |
597 | gvputs(job, ";\"/>\n</radialGradient>\n</defs>\n" ); |
598 | return id; |
599 | } |
600 | |
601 | |
602 | static void svg_ellipse(GVJ_t * job, pointf * A, int filled) |
603 | { |
604 | int gid = 0; |
605 | |
606 | /* A[] contains 2 points: the center and corner. */ |
607 | if (filled == GRADIENT) { |
608 | gid = svg_gradstyle(job, A, 2); |
609 | } else if (filled == (RGRADIENT)) { |
610 | gid = svg_rgradstyle(job, A, 2); |
611 | } |
612 | gvputs(job, "<ellipse" ); |
613 | svg_grstyle(job, filled, gid); |
614 | gvputs(job, " cx=\"" ); |
615 | gvprintdouble(job, A[0].x); |
616 | gvputs(job, "\" cy=\"" ); |
617 | gvprintdouble(job, -A[0].y); |
618 | gvputs(job, "\" rx=\"" ); |
619 | gvprintdouble(job, A[1].x - A[0].x); |
620 | gvputs(job, "\" ry=\"" ); |
621 | gvprintdouble(job, A[1].y - A[0].y); |
622 | gvputs(job, "\"/>\n" ); |
623 | } |
624 | |
625 | static void |
626 | svg_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start, |
627 | int arrow_at_end, int filled) |
628 | { |
629 | int gid = 0; |
630 | obj_state_t *obj = job->obj; |
631 | |
632 | if (filled == GRADIENT) { |
633 | gid = svg_gradstyle(job, A, n); |
634 | } else if (filled == (RGRADIENT)) { |
635 | gid = svg_rgradstyle(job, A, n); |
636 | } |
637 | gvputs(job, "<path" ); |
638 | if (obj->labeledgealigned) { |
639 | gvputs(job, " id=\"" ); |
640 | gvputs(job, xml_string(obj->id)); |
641 | gvputs(job, "_p\" " ); |
642 | } |
643 | svg_grstyle(job, filled, gid); |
644 | gvputs(job, " d=\"" ); |
645 | svg_bzptarray(job, A, n); |
646 | gvputs(job, "\"/>\n" ); |
647 | } |
648 | |
649 | static void svg_polygon(GVJ_t * job, pointf * A, int n, int filled) |
650 | { |
651 | int i, gid = 0; |
652 | if (filled == GRADIENT) { |
653 | gid = svg_gradstyle(job, A, n); |
654 | } else if (filled == (RGRADIENT)) { |
655 | gid = svg_rgradstyle(job, A, n); |
656 | } |
657 | gvputs(job, "<polygon" ); |
658 | svg_grstyle(job, filled, gid); |
659 | gvputs(job, " points=\"" ); |
660 | for (i = 0; i < n; i++) { |
661 | gvprintdouble(job, A[i].x); |
662 | gvputs(job, "," ); |
663 | gvprintdouble(job, -A[i].y); |
664 | gvputs(job, " " ); |
665 | } |
666 | /* repeat the first point because Adobe SVG is broken */ |
667 | gvprintdouble(job, A[0].x); |
668 | gvputs(job, "," ); |
669 | gvprintdouble(job, -A[0].y); |
670 | gvputs(job, "\"/>\n" ); |
671 | } |
672 | |
673 | static void svg_polyline(GVJ_t * job, pointf * A, int n) |
674 | { |
675 | int i; |
676 | |
677 | gvputs(job, "<polyline" ); |
678 | svg_grstyle(job, 0, 0); |
679 | gvputs(job, " points=\"" ); |
680 | for (i = 0; i < n; i++) { |
681 | gvprintdouble(job, A[i].x); |
682 | gvputs(job, "," ); |
683 | gvprintdouble(job, -A[i].y); |
684 | gvputs(job, " " ); |
685 | } |
686 | gvputs(job, "\"/>\n" ); |
687 | } |
688 | |
689 | /* color names from http://www.w3.org/TR/SVG/types.html */ |
690 | /* NB. List must be LANG_C sorted */ |
691 | static char *svg_knowncolors[] = { |
692 | "aliceblue" , "antiquewhite" , "aqua" , "aquamarine" , "azure" , |
693 | "beige" , "bisque" , "black" , "blanchedalmond" , "blue" , |
694 | "blueviolet" , "brown" , "burlywood" , |
695 | "cadetblue" , "chartreuse" , "chocolate" , "coral" , |
696 | "cornflowerblue" , "cornsilk" , "crimson" , "cyan" , |
697 | "darkblue" , "darkcyan" , "darkgoldenrod" , "darkgray" , |
698 | "darkgreen" , "darkgrey" , "darkkhaki" , "darkmagenta" , |
699 | "darkolivegreen" , "darkorange" , "darkorchid" , "darkred" , |
700 | "darksalmon" , "darkseagreen" , "darkslateblue" , "darkslategray" , |
701 | "darkslategrey" , "darkturquoise" , "darkviolet" , "deeppink" , |
702 | "deepskyblue" , "dimgray" , "dimgrey" , "dodgerblue" , |
703 | "firebrick" , "floralwhite" , "forestgreen" , "fuchsia" , |
704 | "gainsboro" , "ghostwhite" , "gold" , "goldenrod" , "gray" , |
705 | "green" , "greenyellow" , "grey" , |
706 | "honeydew" , "hotpink" , "indianred" , |
707 | "indigo" , "ivory" , "khaki" , |
708 | "lavender" , "lavenderblush" , "lawngreen" , "lemonchiffon" , |
709 | "lightblue" , "lightcoral" , "lightcyan" , "lightgoldenrodyellow" , |
710 | "lightgray" , "lightgreen" , "lightgrey" , "lightpink" , |
711 | "lightsalmon" , "lightseagreen" , "lightskyblue" , |
712 | "lightslategray" , "lightslategrey" , "lightsteelblue" , |
713 | "lightyellow" , "lime" , "limegreen" , "linen" , |
714 | "magenta" , "maroon" , "mediumaquamarine" , "mediumblue" , |
715 | "mediumorchid" , "mediumpurple" , "mediumseagreen" , |
716 | "mediumslateblue" , "mediumspringgreen" , "mediumturquoise" , |
717 | "mediumvioletred" , "midnightblue" , "mintcream" , |
718 | "mistyrose" , "moccasin" , |
719 | "navajowhite" , "navy" , "oldlace" , |
720 | "olive" , "olivedrab" , "orange" , "orangered" , "orchid" , |
721 | "palegoldenrod" , "palegreen" , "paleturquoise" , |
722 | "palevioletred" , "papayawhip" , "peachpuff" , "peru" , "pink" , |
723 | "plum" , "powderblue" , "purple" , |
724 | "red" , "rosybrown" , "royalblue" , |
725 | "saddlebrown" , "salmon" , "sandybrown" , "seagreen" , "seashell" , |
726 | "sienna" , "silver" , "skyblue" , "slateblue" , "slategray" , |
727 | "slategrey" , "snow" , "springgreen" , "steelblue" , |
728 | "tan" , "teal" , "thistle" , "tomato" , "turquoise" , |
729 | "violet" , |
730 | "wheat" , "white" , "whitesmoke" , |
731 | "yellow" , "yellowgreen" |
732 | }; |
733 | |
734 | gvrender_engine_t svg_engine = { |
735 | svg_begin_job, |
736 | 0, /* svg_end_job */ |
737 | svg_begin_graph, |
738 | svg_end_graph, |
739 | svg_begin_layer, |
740 | svg_end_layer, |
741 | svg_begin_page, |
742 | svg_end_page, |
743 | svg_begin_cluster, |
744 | svg_end_cluster, |
745 | 0, /* svg_begin_nodes */ |
746 | 0, /* svg_end_nodes */ |
747 | 0, /* svg_begin_edges */ |
748 | 0, /* svg_end_edges */ |
749 | svg_begin_node, |
750 | svg_end_node, |
751 | svg_begin_edge, |
752 | svg_end_edge, |
753 | svg_begin_anchor, |
754 | svg_end_anchor, |
755 | 0, /* svg_begin_anchor */ |
756 | 0, /* svg_end_anchor */ |
757 | svg_textspan, |
758 | 0, /* svg_resolve_color */ |
759 | svg_ellipse, |
760 | svg_polygon, |
761 | svg_bezier, |
762 | svg_polyline, |
763 | svg_comment, |
764 | 0, /* svg_library_shape */ |
765 | }; |
766 | |
767 | gvrender_features_t render_features_svg = { |
768 | GVRENDER_Y_GOES_DOWN | GVRENDER_DOES_TRANSFORM | GVRENDER_DOES_LABELS | GVRENDER_DOES_MAPS | GVRENDER_DOES_TARGETS | GVRENDER_DOES_TOOLTIPS, /* flags */ |
769 | 4., /* default pad - graph units */ |
770 | svg_knowncolors, /* knowncolors */ |
771 | sizeof(svg_knowncolors) / sizeof(char *), /* sizeof knowncolors */ |
772 | RGBA_BYTE, /* color_type */ |
773 | }; |
774 | |
775 | gvdevice_features_t device_features_svg = { |
776 | GVDEVICE_DOES_TRUECOLOR|GVDEVICE_DOES_LAYERS, /* flags */ |
777 | {0., 0.}, /* default margin - points */ |
778 | {0., 0.}, /* default page width, height - points */ |
779 | {72., 72.}, /* default dpi */ |
780 | }; |
781 | |
782 | gvdevice_features_t device_features_svgz = { |
783 | GVDEVICE_DOES_TRUECOLOR|GVDEVICE_DOES_LAYERS|GVDEVICE_BINARY_FORMAT|GVDEVICE_COMPRESSED_FORMAT, /* flags */ |
784 | {0., 0.}, /* default margin - points */ |
785 | {0., 0.}, /* default page width, height - points */ |
786 | {72., 72.}, /* default dpi */ |
787 | }; |
788 | |
789 | gvplugin_installed_t gvrender_svg_types[] = { |
790 | {FORMAT_SVG, "svg" , 1, &svg_engine, &render_features_svg}, |
791 | {0, NULL, 0, NULL, NULL} |
792 | }; |
793 | |
794 | gvplugin_installed_t gvdevice_svg_types[] = { |
795 | {FORMAT_SVG, "svg:svg" , 1, NULL, &device_features_svg}, |
796 | #if HAVE_LIBZ |
797 | {FORMAT_SVGZ, "svgz:svg" , 1, NULL, &device_features_svgz}, |
798 | #endif |
799 | {0, NULL, 0, NULL, NULL} |
800 | }; |
801 | |