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
41typedef enum { FORMAT_SVG, FORMAT_SVGZ, } format_type;
42
43/* SVG dash array */
44static char *sdasharray = "5,2";
45/* SVG dot array */
46static char *sdotarray = "1,5";
47
48#ifndef HAVE_STRCASECMP
49extern int strcasecmp(const char *s1, const char *s2);
50#endif
51
52static 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
87static 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
103static 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
121static 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
159static void svg_comment(GVJ_t * job, char *str)
160{
161 gvputs(job, "<!-- ");
162 gvputs(job, xml_string(str));
163 gvputs(job, " -->\n");
164}
165
166static 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
199static 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
225static void svg_end_graph(GVJ_t * job)
226{
227 gvputs(job, "</svg>\n");
228}
229
230static 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
239static 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 */
248static 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
272static void svg_end_page(GVJ_t * job)
273{
274 gvputs(job, "</g>\n");
275}
276
277static 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
288static void svg_end_cluster(GVJ_t * job)
289{
290 gvputs(job, "</g>\n");
291}
292
293static 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
309static void svg_end_node(GVJ_t * job)
310{
311 gvputs(job, "</g>\n");
312}
313
314static 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
329static void svg_end_edge(GVJ_t * job)
330{
331 gvputs(job, "</g>\n");
332}
333
334static void
335svg_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
380static void svg_end_anchor(GVJ_t * job)
381{
382 gvputs(job, "</a>\n");
383 gvputs(job, "</g>\n");
384}
385
386static 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 */
506static 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 */
559static 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
602static 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
625static void
626svg_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
649static 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
673static 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 */
691static 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
734gvrender_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
767gvrender_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
775gvdevice_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
782gvdevice_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
789gvplugin_installed_t gvrender_svg_types[] = {
790 {FORMAT_SVG, "svg", 1, &svg_engine, &render_features_svg},
791 {0, NULL, 0, NULL, NULL}
792};
793
794gvplugin_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