/misc/src/release/graphviz-2.18-1/src/graphviz-2.18/plugin/core/gvrender_core_svg.c

Go to the documentation of this file.
00001 /* $Id: gvrender_core_svg.c,v 1.62 2007/11/27 20:02:32 erg Exp $ $Revision: 1.62 $ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /**********************************************************
00005 *      This software is part of the graphviz package      *
00006 *                http://www.graphviz.org/                 *
00007 *                                                         *
00008 *            Copyright (c) 1994-2004 AT&T Corp.           *
00009 *                and is licensed under the                *
00010 *            Common Public License, Version 1.0           *
00011 *                      by AT&T Corp.                      *
00012 *                                                         *
00013 *        Information and Software Systems Research        *
00014 *              AT&T Research, Florham Park NJ             *
00015 **********************************************************/
00016 
00017 /* Comments on the SVG coordinate system (SN 8 Dec 2006):
00018    The initial <svg> element defines the SVG coordinate system so
00019    that the graphviz canvas (in units of points) fits the intended
00020    absolute size in inches.  After this, the situation should be
00021    that "px" = "pt" in SVG, so we can dispense with stating units.
00022    Also, the input units (such as fontsize) should be preserved
00023    without scaling in the output SVG (as long as the graph size
00024    was not constrained.)
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"              /* need the SVG font name schemes */
00043 
00044 typedef enum { FORMAT_SVG, FORMAT_SVGZ, } format_type;
00045 
00046 extern char *xml_string(char *str);
00047 
00048 /* SVG dash array */
00049 static char *sdarray = "5,2";
00050 /* SVG dot array */
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';                    /* first point */
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';            /* second point */
00063         else
00064             c = ' ';            /* remaining points */
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) /* transparent */
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);              /* internal error */
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 /* isAscii:
00115  * Return true if all characters in the string are ascii.
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     /* This is to work around a bug in the SVG 1.0 DTD */
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     /* We have absolutely no idea what character set the username
00148      * may be in. To be conservative, we only output the username
00149      * if it is all ascii. Since SVG output is UTF-8, we could check
00150      * if the string appears to be in this format and allow it.
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     /* namespace of svg */
00178     gvdevice_fputs(job, " xmlns=\"http://www.w3.org/2000/svg\"");
00179     /* namespace of xlink */
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     /* its really just a page of the graph, but its still a graph,
00206      * and it is the entire graph if we're not currently paging */
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     /* default style */
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 = "&#45;&gt;";
00264     else
00265         edgeop = "&#45;&#45;";
00266     gvdevice_fputs(job, "<title>");
00267     gvdevice_fputs(job, xml_string(obj->u.e->tail->name));
00268     gvdevice_fputs(job, edgeop);
00269     /* can't do this in single gvdevice_printf because
00270      * xml_string's buffer gets reused. */
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);              /* internal error */
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     /* A[] contains 2 points: the center and corner. */
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);     /* because Adobe SVG is broken */
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 /* color names from http://www.w3.org/TR/SVG/types.html */
00415 /* NB.  List must be LANG_C sorted */
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,                          /* svg_end_job */
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,                          /* svg_begin_nodes */
00471     0,                          /* svg_end_nodes */
00472     0,                          /* svg_begin_edges */
00473     0,                          /* svg_end_edges */
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,                          /* svg_resolve_color */
00482     svg_ellipse,
00483     svg_polygon,
00484     svg_bezier,
00485     svg_polyline,
00486     svg_comment,
00487     0,                          /* svg_library_shape */
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, /* flags */
00497     4.,                         /* default pad - graph units */
00498     svg_knowncolors,            /* knowncolors */
00499     sizeof(svg_knowncolors) / sizeof(char *),   /* sizeof knowncolors */
00500     RGBA_BYTE,                  /* color_type */
00501 };
00502 
00503 gvdevice_features_t device_features_svg = {
00504     GVDEVICE_DOES_TRUECOLOR,    /* flags */
00505     {0.,0.},                    /* default margin - points */
00506     {0.,0.},                    /* default page width, height - points */
00507     {72.,72.},                  /* default dpi */
00508 };
00509 
00510 gvdevice_features_t device_features_svgz = {
00511     GVDEVICE_BINARY_FORMAT
00512       | GVDEVICE_COMPRESSED_FORMAT
00513       | GVDEVICE_DOES_TRUECOLOR,/* flags */
00514     {0.,0.},                    /* default margin - points */
00515     {0.,0.},                    /* default page width, height - points */
00516     {72.,72.},                  /* default dpi */
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 };

Generated on Mon Mar 31 19:03:28 2008 for Graphviz by  doxygen 1.5.1