00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 #ifdef HAVE_CONFIG_H
00028 #include "config.h"
00029 #endif
00030
00031 #include <stdarg.h>
00032 #include <stdlib.h>
00033 #include <string.h>
00034 #include <ctype.h>
00035
00036 #include "macros.h"
00037 #include "const.h"
00038
00039 #include "gvplugin_render.h"
00040 #include "gvcint.h"
00041 #include "graph.h"
00042 #include "types.h"
00043
00044 typedef enum { FORMAT_SVG, FORMAT_SVGZ, } format_type;
00045
00046 extern char *xml_string(char *str);
00047
00048
00049 static char *sdarray = "5,2";
00050
00051 static char *sdotarray = "1,5";
00052
00053 static void svg_bzptarray(GVJ_t * job, pointf * A, int n)
00054 {
00055 int i;
00056 char c;
00057
00058 c = 'M';
00059 for (i = 0; i < n; i++) {
00060 gvdevice_printf(job, "%c%g,%g", c, A[i].x, -A[i].y);
00061 if (i == 0)
00062 c = 'C';
00063 else
00064 c = ' ';
00065 }
00066 }
00067
00068 static void svg_print_color(GVJ_t * job, gvcolor_t color)
00069 {
00070 switch (color.type) {
00071 case COLOR_STRING:
00072 gvdevice_fputs(job, color.u.string);
00073 break;
00074 case RGBA_BYTE:
00075 if (color.u.rgba[3] == 0)
00076 gvdevice_fputs(job, "none");
00077 else
00078 gvdevice_printf(job, "#%02x%02x%02x",
00079 color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
00080 break;
00081 default:
00082 assert(0);
00083 }
00084 }
00085
00086 static void svg_grstyle(GVJ_t * job, int filled)
00087 {
00088 obj_state_t *obj = job->obj;
00089
00090 gvdevice_fputs(job, " style=\"fill:");
00091 if (filled)
00092 svg_print_color(job, obj->fillcolor);
00093 else
00094 gvdevice_fputs(job, "none");
00095 gvdevice_fputs(job, ";stroke:");
00096 svg_print_color(job, obj->pencolor);
00097 if (obj->penwidth != PENWIDTH_NORMAL)
00098 gvdevice_printf(job, ";stroke-width:%g", obj->penwidth);
00099 if (obj->pen == PEN_DASHED) {
00100 gvdevice_printf(job, ";stroke-dasharray:%s", sdarray);
00101 } else if (obj->pen == PEN_DOTTED) {
00102 gvdevice_printf(job, ";stroke-dasharray:%s", sdotarray);
00103 }
00104 gvdevice_fputs(job, ";\"");
00105 }
00106
00107 static void svg_comment(GVJ_t * job, char *str)
00108 {
00109 gvdevice_fputs(job, "<!-- ");
00110 gvdevice_fputs(job, xml_string(str));
00111 gvdevice_fputs(job, " -->\n");
00112 }
00113
00114
00115
00116
00117 static int isAscii (char* s)
00118 {
00119 int c;
00120 while ((c = *s++) != '\0') {
00121 if (!isascii (c)) return 0;
00122 }
00123 return 1;
00124 }
00125
00126 static void svg_begin_job(GVJ_t * job)
00127 {
00128 char *s;
00129 gvdevice_fputs(job, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
00130 if ((s = agget(job->gvc->g, "stylesheet")) && s[0]) {
00131 gvdevice_fputs(job, "<?xml-stylesheet href=\"");
00132 gvdevice_fputs(job, s);
00133 gvdevice_fputs(job, "\" type=\"text/css\"?>\n");
00134 }
00135 gvdevice_fputs(job, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n");
00136 gvdevice_fputs(job, " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"");
00137
00138
00139 gvdevice_fputs(job, " [\n <!ATTLIST svg xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\">\n]");
00140
00141 gvdevice_fputs(job, ">\n<!-- Generated by ");
00142 gvdevice_fputs(job, xml_string(job->common->info[0]));
00143 gvdevice_fputs(job, " version ");
00144 gvdevice_fputs(job, xml_string(job->common->info[1]));
00145 gvdevice_fputs(job, " (");
00146 gvdevice_fputs(job, xml_string(job->common->info[2]));
00147
00148
00149
00150
00151
00152 if (isAscii (job->common->user)) {
00153 gvdevice_fputs(job, ")\n For user: ");
00154 gvdevice_fputs(job, xml_string(job->common->user));
00155 }
00156 else
00157 gvdevice_fputs(job, ")\n");
00158 gvdevice_fputs(job, " -->\n");
00159 }
00160
00161 static void svg_begin_graph(GVJ_t * job)
00162 {
00163 obj_state_t *obj = job->obj;
00164
00165 gvdevice_fputs(job, "<!--");
00166 if (obj->u.g->name[0]) {
00167 gvdevice_fputs(job, " Title: ");
00168 gvdevice_fputs(job, xml_string(obj->u.g->name));
00169 }
00170 gvdevice_printf(job, " Pages: %d -->\n", job->pagesArraySize.x * job->pagesArraySize.y);
00171
00172 gvdevice_printf(job, "<svg width=\"%dpt\" height=\"%dpt\"\n",
00173 job->width, job->height);
00174 gvdevice_printf(job, " viewBox=\"%.2f %.2f %.2f %.2f\"",
00175 job->canvasBox.LL.x, job->canvasBox.LL.y,
00176 job->canvasBox.UR.x, job->canvasBox.UR.y);
00177
00178 gvdevice_fputs(job, " xmlns=\"http://www.w3.org/2000/svg\"");
00179
00180 gvdevice_fputs(job, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
00181 gvdevice_fputs(job, ">\n");
00182 }
00183
00184 static void svg_end_graph(GVJ_t * job)
00185 {
00186 gvdevice_fputs(job, "</svg>\n");
00187 }
00188
00189 static void svg_begin_layer(GVJ_t * job, char *layername, int layerNum, int numLayers)
00190 {
00191 gvdevice_fputs(job, "<g id=\"");
00192 gvdevice_fputs(job, xml_string(layername));
00193 gvdevice_fputs(job, "\" class=\"layer\">\n");
00194 }
00195
00196 static void svg_end_layer(GVJ_t * job)
00197 {
00198 gvdevice_fputs(job, "</g>\n");
00199 }
00200
00201 static void svg_begin_page(GVJ_t * job)
00202 {
00203 obj_state_t *obj = job->obj;
00204
00205
00206
00207 gvdevice_printf(job, "<g id=\"graph%d\" class=\"graph\"", job->common->viewNum);
00208 gvdevice_printf(job, " transform=\"scale(%g %g) rotate(%d) translate(%g %g)\">\n",
00209 job->scale.x, job->scale.y, -job->rotation,
00210 job->translation.x, -job->translation.y);
00211
00212 if (obj->u.g->name[0]) {
00213 gvdevice_fputs(job, "<title>");
00214 gvdevice_fputs(job, xml_string(obj->u.g->name));
00215 gvdevice_fputs(job, "</title>\n");
00216 }
00217 }
00218
00219 static void svg_end_page(GVJ_t * job)
00220 {
00221 gvdevice_fputs(job, "</g>\n");
00222 }
00223
00224 static void svg_begin_cluster(GVJ_t * job)
00225 {
00226 obj_state_t *obj = job->obj;
00227
00228 gvdevice_printf(job, "<g id=\"cluster%ld\" class=\"cluster\">",
00229 obj->u.sg->meta_node->id);
00230 gvdevice_fputs(job, "<title>");
00231 gvdevice_fputs(job, xml_string(obj->u.sg->name));
00232 gvdevice_fputs(job, "</title>\n");
00233 }
00234
00235 static void svg_end_cluster(GVJ_t * job)
00236 {
00237 gvdevice_fputs(job, "</g>\n");
00238 }
00239
00240 static void svg_begin_node(GVJ_t * job)
00241 {
00242 obj_state_t *obj = job->obj;
00243
00244 gvdevice_printf(job, "<g id=\"node%ld\" class=\"node\">", obj->u.n->id);
00245 gvdevice_fputs(job, "<title>");
00246 gvdevice_fputs(job, xml_string(obj->u.n->name));
00247 gvdevice_fputs(job, "</title>\n");
00248 }
00249
00250 static void svg_end_node(GVJ_t * job)
00251 {
00252 gvdevice_fputs(job, "</g>\n");
00253 }
00254
00255 static void
00256 svg_begin_edge(GVJ_t * job)
00257 {
00258 obj_state_t *obj = job->obj;
00259 char *edgeop;
00260
00261 gvdevice_printf(job, "<g id=\"edge%ld\" class=\"edge\">", obj->u.e->id);
00262 if (obj->u.e->tail->graph->root->kind & AGFLAG_DIRECTED)
00263 edgeop = "->";
00264 else
00265 edgeop = "--";
00266 gvdevice_fputs(job, "<title>");
00267 gvdevice_fputs(job, xml_string(obj->u.e->tail->name));
00268 gvdevice_fputs(job, edgeop);
00269
00270
00271 gvdevice_fputs(job, xml_string(obj->u.e->head->name));
00272 gvdevice_fputs(job, "</title>\n");
00273 }
00274
00275 static void svg_end_edge(GVJ_t * job)
00276 {
00277 gvdevice_fputs(job, "</g>\n");
00278 }
00279
00280 static void
00281 svg_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target)
00282 {
00283 gvdevice_fputs(job, "<a");
00284 if (href && href[0])
00285 gvdevice_printf(job, " xlink:href=\"%s\"", xml_string(href));
00286 if (tooltip && tooltip[0])
00287 gvdevice_printf(job, " xlink:title=\"%s\"", xml_string(tooltip));
00288 if (target && target[0])
00289 gvdevice_printf(job, " target=\"%s\"", xml_string(target));
00290 gvdevice_fputs(job, ">\n");
00291 }
00292
00293 static void svg_end_anchor(GVJ_t * job)
00294 {
00295 gvdevice_fputs(job, "</a>\n");
00296 }
00297
00298 static void svg_textpara(GVJ_t * job, pointf p, textpara_t * para)
00299 {
00300 obj_state_t *obj = job->obj;
00301 PostscriptAlias *pA;
00302
00303 gvdevice_fputs(job, "<text");
00304 switch (para->just) {
00305 case 'l':
00306 gvdevice_fputs(job, " text-anchor=\"start\"");
00307 break;
00308 case 'r':
00309 gvdevice_fputs(job, " text-anchor=\"end\"");
00310 break;
00311 default:
00312 case 'n':
00313 gvdevice_fputs(job, " text-anchor=\"middle\"");
00314 break;
00315 }
00316 p.y += para->yoffset_centerline;
00317 gvdevice_printf(job, " x=\"%g\" y=\"%g\"", p.x, -p.y);
00318 gvdevice_fputs(job, " style=\"");
00319 pA = para->postscript_alias;
00320 if (pA) {
00321 char *family=NULL, *weight=NULL, *stretch=NULL, *style=NULL;
00322 switch(GD_fontnames(job->gvc->g)) {
00323 case PSFONTS:
00324 family = pA->name;
00325 weight = pA->weight;
00326 style = pA->style;
00327 break;
00328 case SVGFONTS:
00329 family = pA->svg_font_family;
00330 weight = pA->svg_font_weight;
00331 style = pA->svg_font_style;
00332 break;
00333 default:
00334 case NATIVEFONTS:
00335 family = pA->family;
00336 weight = pA->weight;
00337 style = pA->style;
00338 break;
00339 }
00340 stretch = pA->stretch;
00341
00342 gvdevice_printf(job, "font-family:%s;", family);
00343 if (weight) gvdevice_printf(job, "font-weight:%s;", weight);
00344 if (stretch) gvdevice_printf(job, "font-stretch:%s;", stretch);
00345 if (style) gvdevice_printf(job, "font-style:%s;", style);
00346 }
00347 else
00348 gvdevice_printf(job, "font-family:%s;", para->fontname);
00349 gvdevice_printf(job, "font-size:%.2f;", para->fontsize);
00350 switch (obj->pencolor.type) {
00351 case COLOR_STRING:
00352 if (strcasecmp(obj->pencolor.u.string, "black"))
00353 gvdevice_printf(job, "fill:%s;", obj->pencolor.u.string);
00354 break;
00355 case RGBA_BYTE:
00356 gvdevice_printf(job, "fill:#%02x%02x%02x;",
00357 obj->pencolor.u.rgba[0], obj->pencolor.u.rgba[1], obj->pencolor.u.rgba[2]);
00358 break;
00359 default:
00360 assert(0);
00361 }
00362 gvdevice_fputs(job, "\">");
00363 gvdevice_fputs(job, xml_string(para->str));
00364 gvdevice_fputs(job, "</text>\n");
00365 }
00366
00367 static void svg_ellipse(GVJ_t * job, pointf * A, int filled)
00368 {
00369
00370 gvdevice_fputs(job, "<ellipse");
00371 svg_grstyle(job, filled);
00372 gvdevice_printf(job, " cx=\"%g\" cy=\"%g\"", A[0].x, -A[0].y);
00373 gvdevice_printf(job, " rx=\"%g\" ry=\"%g\"",
00374 A[1].x - A[0].x, A[1].y - A[0].y);
00375 gvdevice_fputs(job, "/>\n");
00376 }
00377
00378 static void
00379 svg_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
00380 int arrow_at_end, int filled)
00381 {
00382 gvdevice_fputs(job, "<path");
00383 svg_grstyle(job, filled);
00384 gvdevice_fputs(job, " d=\"");
00385 svg_bzptarray(job, A, n);
00386 gvdevice_fputs(job, "\"/>\n");
00387 }
00388
00389 static void svg_polygon(GVJ_t * job, pointf * A, int n, int filled)
00390 {
00391 int i;
00392
00393 gvdevice_fputs(job, "<polygon");
00394 svg_grstyle(job, filled);
00395 gvdevice_fputs(job, " points=\"");
00396 for (i = 0; i < n; i++)
00397 gvdevice_printf(job, "%g,%g ", A[i].x, -A[i].y);
00398 gvdevice_printf(job, "%g,%g", A[0].x, -A[0].y);
00399 gvdevice_fputs(job, "\"/>\n");
00400 }
00401
00402 static void svg_polyline(GVJ_t * job, pointf * A, int n)
00403 {
00404 int i;
00405
00406 gvdevice_fputs(job, "<polyline");
00407 svg_grstyle(job, 0);
00408 gvdevice_fputs(job, " points=\"");
00409 for (i = 0; i < n; i++)
00410 gvdevice_printf(job, "%g,%g ", A[i].x, -A[i].y);
00411 gvdevice_fputs(job, "\"/>\n");
00412 }
00413
00414
00415
00416 static char *svg_knowncolors[] = {
00417 "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
00418 "beige", "bisque", "black", "blanchedalmond", "blue",
00419 "blueviolet", "brown", "burlywood",
00420 "cadetblue", "chartreuse", "chocolate", "coral",
00421 "cornflowerblue", "cornsilk", "crimson", "cyan",
00422 "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
00423 "darkgreen", "darkgrey", "darkkhaki", "darkmagenta",
00424 "darkolivegreen", "darkorange", "darkorchid", "darkred",
00425 "darksalmon", "darkseagreen", "darkslateblue", "darkslategray",
00426 "darkslategrey", "darkturquoise", "darkviolet", "deeppink",
00427 "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
00428 "firebrick", "floralwhite", "forestgreen", "fuchsia",
00429 "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
00430 "green", "greenyellow", "grey",
00431 "honeydew", "hotpink", "indianred",
00432 "indigo", "ivory", "khaki",
00433 "lavender", "lavenderblush", "lawngreen", "lemonchiffon",
00434 "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
00435 "lightgray", "lightgreen", "lightgrey", "lightpink",
00436 "lightsalmon", "lightseagreen", "lightskyblue",
00437 "lightslategray", "lightslategrey", "lightsteelblue",
00438 "lightyellow", "lime", "limegreen", "linen",
00439 "magenta", "maroon", "mediumaquamarine", "mediumblue",
00440 "mediumorchid", "mediumpurple", "mediumseagreen",
00441 "mediumslateblue", "mediumspringgreen", "mediumturquoise",
00442 "mediumvioletred", "midnightblue", "mintcream",
00443 "mistyrose", "moccasin",
00444 "navajowhite", "navy", "oldlace",
00445 "olive", "olivedrab", "orange", "orangered", "orchid",
00446 "palegoldenrod", "palegreen", "paleturquoise",
00447 "palevioletred", "papayawhip", "peachpuff", "peru", "pink",
00448 "plum", "powderblue", "purple",
00449 "red", "rosybrown", "royalblue",
00450 "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell",
00451 "sienna", "silver", "skyblue", "slateblue", "slategray",
00452 "slategrey", "snow", "springgreen", "steelblue",
00453 "tan", "teal", "thistle", "tomato", "turquoise",
00454 "violet",
00455 "wheat", "white", "whitesmoke",
00456 "yellow", "yellowgreen"
00457 };
00458
00459 gvrender_engine_t svg_engine = {
00460 svg_begin_job,
00461 0,
00462 svg_begin_graph,
00463 svg_end_graph,
00464 svg_begin_layer,
00465 svg_end_layer,
00466 svg_begin_page,
00467 svg_end_page,
00468 svg_begin_cluster,
00469 svg_end_cluster,
00470 0,
00471 0,
00472 0,
00473 0,
00474 svg_begin_node,
00475 svg_end_node,
00476 svg_begin_edge,
00477 svg_end_edge,
00478 svg_begin_anchor,
00479 svg_end_anchor,
00480 svg_textpara,
00481 0,
00482 svg_ellipse,
00483 svg_polygon,
00484 svg_bezier,
00485 svg_polyline,
00486 svg_comment,
00487 0,
00488 };
00489
00490 gvrender_features_t render_features_svg = {
00491 GVRENDER_Y_GOES_DOWN
00492 | GVRENDER_DOES_TRANSFORM
00493 | GVRENDER_DOES_LABELS
00494 | GVRENDER_DOES_MAPS
00495 | GVRENDER_DOES_TARGETS
00496 | GVRENDER_DOES_TOOLTIPS,
00497 4.,
00498 svg_knowncolors,
00499 sizeof(svg_knowncolors) / sizeof(char *),
00500 RGBA_BYTE,
00501 };
00502
00503 gvdevice_features_t device_features_svg = {
00504 GVDEVICE_DOES_TRUECOLOR,
00505 {0.,0.},
00506 {0.,0.},
00507 {72.,72.},
00508 };
00509
00510 gvdevice_features_t device_features_svgz = {
00511 GVDEVICE_BINARY_FORMAT
00512 | GVDEVICE_COMPRESSED_FORMAT
00513 | GVDEVICE_DOES_TRUECOLOR,
00514 {0.,0.},
00515 {0.,0.},
00516 {72.,72.},
00517 };
00518
00519 gvplugin_installed_t gvrender_svg_types[] = {
00520 {FORMAT_SVG, "svg", 1, &svg_engine, &render_features_svg},
00521 {0, NULL, 0, NULL, NULL}
00522 };
00523
00524 gvplugin_installed_t gvdevice_svg_types[] = {
00525 {FORMAT_SVG, "svg:svg", 1, NULL, &device_features_svg},
00526 #if HAVE_LIBZ
00527 {FORMAT_SVGZ, "svgz:svg", 1, NULL, &device_features_svgz},
00528 #endif
00529 {0, NULL, 0, NULL, NULL}
00530 };