Graphviz  2.29.20120524.0446
plugin/core/gvrender_core_svg.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 /* Comments on the SVG coordinate system (SN 8 Dec 2006):
00015    The initial <svg> element defines the SVG coordinate system so
00016    that the graphviz canvas (in units of points) fits the intended
00017    absolute size in inches.  After this, the situation should be
00018    that "px" = "pt" in SVG, so we can dispense with stating units.
00019    Also, the input units (such as fontsize) should be preserved
00020    without scaling in the output SVG (as long as the graph size
00021    was not constrained.)
00022  */
00023 
00024 #ifdef HAVE_CONFIG_H
00025 #include "config.h"
00026 #endif
00027 
00028 #include <stdarg.h>
00029 #include <stdlib.h>
00030 #include <string.h>
00031 #include <ctype.h>
00032 
00033 #include "macros.h"
00034 #include "const.h"
00035 
00036 #include "gvplugin_render.h"
00037 #include "agxbuf.h"
00038 #include "utils.h"
00039 #include "gvplugin_device.h"
00040 #include "gvio.h"
00041 #include "gvcint.h"
00042 
00043 typedef enum { FORMAT_SVG, FORMAT_SVGZ, } format_type;
00044 
00045 /* SVG dash array */
00046 static char *sdasharray = "5,2";
00047 /* SVG dot array */
00048 static char *sdotarray = "1,5";
00049 
00050 #ifndef HAVE_STRCASECMP
00051 extern int strcasecmp(const char *s1, const char *s2);
00052 #endif
00053 
00054 static void svg_bzptarray(GVJ_t * job, pointf * A, int n)
00055 {
00056     int i;
00057     char c;
00058 
00059     c = 'M';                    /* first point */
00060     for (i = 0; i < n; i++) {
00061         gvprintf(job, "%c%g,%g", c, A[i].x, -A[i].y);
00062         if (i == 0)
00063             c = 'C';            /* second point */
00064         else
00065             c = ' ';            /* remaining points */
00066     }
00067 }
00068 
00069 static void svg_print_color(GVJ_t * job, gvcolor_t color)
00070 {
00071     switch (color.type) {
00072     case COLOR_STRING:
00073         gvputs(job, color.u.string);
00074         break;
00075     case RGBA_BYTE:
00076         if (color.u.rgba[3] == 0)       /* transparent */
00077             gvputs(job, "none");
00078         else
00079             gvprintf(job, "#%02x%02x%02x",
00080                      color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
00081         break;
00082     default:
00083         assert(0);              /* internal error */
00084     }
00085 }
00086 
00087 static void svg_grstyle(GVJ_t * job, int filled, int gid)
00088 {
00089     obj_state_t *obj = job->obj;
00090 
00091     gvputs(job, " fill=\"");
00092     if (filled == GRADIENT) {
00093         gvprintf(job, "url(#l_%d)", gid);
00094     } else if (filled == RGRADIENT) {
00095         gvprintf(job, "url(#r_%d)", gid);
00096     } else if (filled) {
00097         svg_print_color(job, obj->fillcolor);
00098         if (obj->fillcolor.type == RGBA_BYTE
00099             && obj->fillcolor.u.rgba[3] > 0
00100             && obj->fillcolor.u.rgba[3] < 255)
00101             gvprintf(job, "\" fill-opacity=\"%f",
00102                      ((float) obj->fillcolor.u.rgba[3] / 255.0));
00103     } else {
00104         gvputs(job, "none");
00105     }
00106     gvputs(job, "\" stroke=\"");
00107     svg_print_color(job, obj->pencolor);
00108     if (obj->penwidth != PENWIDTH_NORMAL)
00109         gvprintf(job, "\" stroke-width=\"%g", obj->penwidth);
00110     if (obj->pen == PEN_DASHED) {
00111         gvprintf(job, "\" stroke-dasharray=\"%s", sdasharray);
00112     } else if (obj->pen == PEN_DOTTED) {
00113         gvprintf(job, "\" stroke-dasharray=\"%s", sdotarray);
00114     }
00115     if (obj->pencolor.type == RGBA_BYTE && obj->pencolor.u.rgba[3] > 0
00116         && obj->pencolor.u.rgba[3] < 255)
00117         gvprintf(job, "\" stroke-opacity=\"%f",
00118                  ((float) obj->pencolor.u.rgba[3] / 255.0));
00119 
00120     gvputs(job, "\"");
00121 }
00122 
00123 static void svg_comment(GVJ_t * job, char *str)
00124 {
00125     gvputs(job, "<!-- ");
00126     gvputs(job, xml_string(str));
00127     gvputs(job, " -->\n");
00128 }
00129 
00130 static void svg_begin_job(GVJ_t * job)
00131 {
00132     char *s;
00133     gvputs(job,
00134            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
00135     if ((s = agget(job->gvc->g, "stylesheet")) && s[0]) {
00136         gvputs(job, "<?xml-stylesheet href=\"");
00137         gvputs(job, s);
00138         gvputs(job, "\" type=\"text/css\"?>\n");
00139     }
00140 #if 0
00141     gvputs(job, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n");
00142     gvputs(job,
00143            " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"");
00144     /* This is to work around a bug in the SVG 1.0 DTD */
00145     gvputs(job,
00146            " [\n <!ATTLIST svg xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\">\n]");
00147 #else
00148     gvputs(job, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n");
00149     gvputs(job,
00150            " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
00151 #endif
00152 
00153     gvputs(job, "<!-- Generated by ");
00154     gvputs(job, xml_string(job->common->info[0]));
00155     gvputs(job, " version ");
00156     gvputs(job, xml_string(job->common->info[1]));
00157     gvputs(job, " (");
00158     gvputs(job, xml_string(job->common->info[2]));
00159     gvputs(job, ")\n");
00160     gvputs(job, " -->\n");
00161 }
00162 
00163 static void svg_begin_graph(GVJ_t * job)
00164 {
00165     obj_state_t *obj = job->obj;
00166 
00167     gvputs(job, "<!--");
00168     if (agnameof(obj->u.g)[0]) {
00169         gvputs(job, " Title: ");
00170         gvputs(job, xml_string(agnameof(obj->u.g)));
00171     }
00172     gvprintf(job, " Pages: %d -->\n",
00173              job->pagesArraySize.x * job->pagesArraySize.y);
00174 
00175     gvprintf(job, "<svg width=\"%dpt\" height=\"%dpt\"\n",
00176              job->width, job->height);
00177     gvprintf(job, " viewBox=\"%.2f %.2f %.2f %.2f\"",
00178              job->canvasBox.LL.x, job->canvasBox.LL.y,
00179              job->canvasBox.UR.x, job->canvasBox.UR.y);
00180     /* namespace of svg */
00181     gvputs(job, " xmlns=\"http://www.w3.org/2000/svg\"");
00182     /* namespace of xlink */
00183     gvputs(job, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
00184     gvputs(job, ">\n");
00185 }
00186 
00187 static void svg_end_graph(GVJ_t * job)
00188 {
00189     gvputs(job, "</svg>\n");
00190 }
00191 
00192 static void svg_begin_layer(GVJ_t * job, char *layername, int layerNum,
00193                             int numLayers)
00194 {
00195     gvputs(job, "<g id=\"");
00196     gvputs(job, xml_string(layername));
00197     gvputs(job, "\" class=\"layer\">\n");
00198 }
00199 
00200 static void svg_end_layer(GVJ_t * job)
00201 {
00202     gvputs(job, "</g>\n");
00203 }
00204 
00205 static void svg_begin_page(GVJ_t * job)
00206 {
00207     obj_state_t *obj = job->obj;
00208 
00209     /* its really just a page of the graph, but its still a graph,
00210      * and it is the entire graph if we're not currently paging */
00211     gvputs(job, "<g id=\"");
00212     gvputs(job, xml_string(obj->id));
00213     gvputs(job, "\" class=\"graph\"");
00214     gvprintf(job,
00215              " transform=\"scale(%g %g) rotate(%d) translate(%g %g)\">\n",
00216              job->scale.x, job->scale.y, -job->rotation,
00217              job->translation.x, -job->translation.y);
00218     /* default style */
00219     if (agnameof(obj->u.g)[0]) {
00220         gvputs(job, "<title>");
00221         gvputs(job, xml_string(agnameof(obj->u.g)));
00222         gvputs(job, "</title>\n");
00223     }
00224 }
00225 
00226 static void svg_end_page(GVJ_t * job)
00227 {
00228     gvputs(job, "</g>\n");
00229 }
00230 
00231 static void svg_begin_cluster(GVJ_t * job)
00232 {
00233     obj_state_t *obj = job->obj;
00234 
00235     gvputs(job, "<g id=\"");
00236     gvputs(job, xml_string(obj->id));
00237     gvputs(job, "\" class=\"cluster\">");
00238     gvputs(job, "<title>");
00239     gvputs(job, xml_string(agnameof(obj->u.g)));
00240     gvputs(job, "</title>\n");
00241 }
00242 
00243 static void svg_end_cluster(GVJ_t * job)
00244 {
00245     gvputs(job, "</g>\n");
00246 }
00247 
00248 static void svg_begin_node(GVJ_t * job)
00249 {
00250     obj_state_t *obj = job->obj;
00251 
00252     gvputs(job, "<g id=\"");
00253     gvputs(job, xml_string(obj->id));
00254     gvputs(job, "\" class=\"node\">");
00255     gvputs(job, "<title>");
00256     gvputs(job, xml_string(agnameof(obj->u.n)));
00257     gvputs(job, "</title>\n");
00258 }
00259 
00260 static void svg_end_node(GVJ_t * job)
00261 {
00262     gvputs(job, "</g>\n");
00263 }
00264 
00265 static void svg_begin_edge(GVJ_t * job)
00266 {
00267     obj_state_t *obj = job->obj;
00268     char *ename;
00269 
00270     gvputs(job, "<g id=\"");
00271     gvputs(job, xml_string(obj->id));
00272     gvputs(job, "\" class=\"edge\">");
00273 
00274     gvputs(job, "<title>");
00275     ename = strdup_and_subst_obj("\\E", (void *) (obj->u.e));
00276     gvputs(job, xml_string(ename));
00277     free(ename);
00278     gvputs(job, "</title>\n");
00279 }
00280 
00281 static void svg_end_edge(GVJ_t * job)
00282 {
00283     gvputs(job, "</g>\n");
00284 }
00285 
00286 static void
00287 svg_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target,
00288                  char *id)
00289 {
00290     gvputs(job, "<g");
00291     if (id) {
00292         gvputs(job, " id=\"");
00293         gvputs(job, xml_string(id));
00294         gvputs(job, "\"");
00295     }
00296     gvputs(job, ">");
00297 
00298     gvputs(job, "<a");
00299 #if 0
00300     /* the svg spec implies this can be omitted: http://www.w3.org/TR/SVG/linking.html#Links */
00301     gvputs(job, " xlink:type=\"simple\"");
00302 #endif
00303     if (href && href[0]) {
00304         gvputs(job, " xlink:href=\"");
00305         gvputs(job, xml_url_string(href));
00306         gvputs(job, "\"");
00307     }
00308 #if 0
00309     /* linking to itself, just so that it can have a xlink:link in the anchor, seems wrong.
00310      * it changes the behavior in browsers, the link apears in the bottom information bar
00311      */
00312     else {
00313         assert(id && id[0]);    /* there should always be an id available */
00314         gvputs(job, " xlink:href=\"#");
00315         gvputs(job, xml_url_string(href));
00316         gvputs(job, "\"");
00317     }
00318 #endif
00319     if (tooltip && tooltip[0]) {
00320         gvputs(job, " xlink:title=\"");
00321         gvputs(job, xml_string(tooltip));
00322         gvputs(job, "\"");
00323     }
00324     if (target && target[0]) {
00325         gvputs(job, " target=\"");
00326         gvputs(job, xml_string(target));
00327         gvputs(job, "\"");
00328     }
00329     gvputs(job, ">\n");
00330 }
00331 
00332 static void svg_end_anchor(GVJ_t * job)
00333 {
00334     gvputs(job, "</a>\n");
00335     gvputs(job, "</g>\n");
00336 }
00337 
00338 static void svg_textpara(GVJ_t * job, pointf p, textpara_t * para)
00339 {
00340     obj_state_t *obj = job->obj;
00341     PostscriptAlias *pA;
00342     char *family = NULL, *weight = NULL, *stretch = NULL, *style = NULL;
00343     int flags;
00344 
00345     gvputs(job, "<text");
00346     switch (para->just) {
00347     case 'l':
00348         gvputs(job, " text-anchor=\"start\"");
00349         break;
00350     case 'r':
00351         gvputs(job, " text-anchor=\"end\"");
00352         break;
00353     default:
00354     case 'n':
00355         gvputs(job, " text-anchor=\"middle\"");
00356         break;
00357     }
00358     p.y += para->yoffset_centerline;
00359     gvprintf(job, " x=\"%g\" y=\"%g\"", p.x, -p.y);
00360     pA = para->postscript_alias;
00361     if (pA) {
00362         switch (GD_fontnames(job->gvc->g)) {
00363         case PSFONTS:
00364             family = pA->name;
00365             weight = pA->weight;
00366             style = pA->style;
00367             break;
00368         case SVGFONTS:
00369             family = pA->svg_font_family;
00370             weight = pA->svg_font_weight;
00371             style = pA->svg_font_style;
00372             break;
00373         default:
00374         case NATIVEFONTS:
00375             family = pA->family;
00376             weight = pA->weight;
00377             style = pA->style;
00378             break;
00379         }
00380         stretch = pA->stretch;
00381 
00382         gvprintf(job, " font-family=\"%s", family);
00383         if (pA->svg_font_family)
00384             gvprintf(job, ",%s", pA->svg_font_family);
00385         gvputs(job, "\"");
00386         if (weight)
00387             gvprintf(job, " font-weight=\"%s\"", weight);
00388         if (stretch)
00389             gvprintf(job, " font-stretch=\"%s\"", stretch);
00390         if (style)
00391             gvprintf(job, " font-style=\"%s\"", style);
00392     } else
00393         gvprintf(job, " font-family=\"%s\"", para->fontname);
00394     if ((para->font) && (flags = para->font->flags)) {
00395         if ((flags & HTML_BF) && !weight)
00396             gvprintf(job, " font-weight=\"bold\"");
00397         if ((flags & HTML_IF) && !style)
00398             gvprintf(job, " font-style=\"italic\"");
00399         if ((flags & HTML_UL))
00400             gvprintf(job, " text-decoration=\"underline\"");
00401         if ((flags & HTML_SUP))
00402             gvprintf(job, " baseline-shift=\"super\"");
00403         if ((flags & HTML_SUB))
00404             gvprintf(job, " baseline-shift=\"sub\"");
00405     }
00406 
00407     gvprintf(job, " font-size=\"%.2f\"", para->fontsize);
00408     switch (obj->pencolor.type) {
00409     case COLOR_STRING:
00410         if (strcasecmp(obj->pencolor.u.string, "black"))
00411             gvprintf(job, " fill=\"%s\"", obj->pencolor.u.string);
00412         break;
00413     case RGBA_BYTE:
00414         gvprintf(job, " fill=\"#%02x%02x%02x\"",
00415                  obj->pencolor.u.rgba[0], obj->pencolor.u.rgba[1],
00416                  obj->pencolor.u.rgba[2]);
00417         break;
00418     default:
00419         assert(0);              /* internal error */
00420     }
00421     gvputs(job, ">");
00422     gvputs(job, xml_string(para->str));
00423     gvputs(job, "</text>\n");
00424 }
00425 
00426 static int gradId;
00427 
00428 /* svg_gradstyle
00429  * Outputs the SVG statements that define the gradient pattern
00430  */
00431 static int svg_gradstyle(GVJ_t * job, pointf * A, int n)
00432 {
00433     pointf G[2];
00434     float angle;
00435     int id = gradId++;
00436 
00437     obj_state_t *obj = job->obj;
00438     angle = obj->gradient_angle * M_PI / 180;   //angle of gradient line
00439     G[0].x = G[0].y = G[1].x = G[1].y = 0.;
00440     get_gradient_points(A, G, n, angle, 0);     //get points on gradient line
00441 
00442     gvprintf(job,
00443              "<defs>\n<linearGradient id=\"l_%d\" gradientUnits=\"userSpaceOnUse\" ", id);
00444     gvprintf(job, "x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\" >\n", G[0].x,
00445              G[0].y, G[1].x, G[1].y);
00446     gvputs(job, "<stop offset=\"0\" style=\"stop-color:");
00447     svg_print_color(job, obj->fillcolor);
00448     gvputs(job, ";stop-opacity:");
00449     if (obj->fillcolor.type == RGBA_BYTE && obj->fillcolor.u.rgba[3] > 0
00450         && obj->fillcolor.u.rgba[3] < 255)
00451         gvprintf(job, "%f", ((float) obj->fillcolor.u.rgba[3] / 255.0));
00452     else
00453         gvputs(job, "1.");
00454     gvputs(job, ";\"/>\n");
00455     gvputs(job, "<stop offset=\"1\" style=\"stop-color:");
00456     svg_print_color(job, obj->stopcolor);
00457     gvputs(job, ";stop-opacity:");
00458     if (obj->stopcolor.type == RGBA_BYTE && obj->stopcolor.u.rgba[3] > 0
00459         && obj->stopcolor.u.rgba[3] < 255)
00460         gvprintf(job, "%f", ((float) obj->stopcolor.u.rgba[3] / 255.0));
00461     else
00462         gvputs(job, "1.");
00463     gvputs(job, ";\"/>\n</linearGradient>\n</defs>\n");
00464     return id;
00465 }
00466 
00467 /* svg_rgradstyle
00468  * Outputs the SVG statements that define the radial gradient pattern
00469  */
00470 static int svg_rgradstyle(GVJ_t * job, pointf * A, int n)
00471 {
00472     pointf G[2];
00473     float angle;
00474     int ifx, ify;
00475     int id = gradId++;
00476 
00477     obj_state_t *obj = job->obj;
00478     angle = obj->gradient_angle * M_PI / 180;   //angle of gradient line
00479     G[0].x = G[0].y = G[1].x = G[1].y;
00480     get_gradient_points(A, G, n, 0, 1);
00481     if (angle == 0.) {
00482         ifx = ify = 50;
00483     } else {
00484         ifx = 50 * (1 + cos(angle));
00485         ify = 50 * (1 - sin(angle));
00486     }
00487     gvprintf(job,
00488              "<defs>\n<radialGradient id=\"r_%d\" cx=\"50%%\" cy=\"50%%\" r=\"75%%\" fx=\"%d%%\" fy=\"%d%%\">\n",
00489              id, ifx, ify);
00490     gvputs(job, "<stop offset=\"0\" style=\"stop-color:");
00491     svg_print_color(job, obj->fillcolor);
00492     gvputs(job, ";stop-opacity:");
00493     if (obj->fillcolor.type == RGBA_BYTE && obj->fillcolor.u.rgba[3] > 0
00494         && obj->fillcolor.u.rgba[3] < 255)
00495         gvprintf(job, "%f", ((float) obj->fillcolor.u.rgba[3] / 255.0));
00496     else
00497         gvputs(job, "1.");
00498     gvputs(job, ";\"/>\n");
00499     gvputs(job, "<stop offset=\"1\" style=\"stop-color:");
00500     svg_print_color(job, obj->stopcolor);
00501     gvputs(job, ";stop-opacity:");
00502     if (obj->stopcolor.type == RGBA_BYTE && obj->stopcolor.u.rgba[3] > 0
00503         && obj->stopcolor.u.rgba[3] < 255)
00504         gvprintf(job, "%f", ((float) obj->stopcolor.u.rgba[3] / 255.0));
00505     else
00506         gvputs(job, "1.");
00507     gvputs(job, ";\"/>\n</radialGradient>\n</defs>\n");
00508     return id;
00509 }
00510 
00511 
00512 static void svg_ellipse(GVJ_t * job, pointf * A, int filled)
00513 {
00514     int gid = 0;
00515 
00516     /* A[] contains 2 points: the center and corner. */
00517     if (filled == GRADIENT) {
00518         gid = svg_gradstyle(job, A, 2);
00519     } else if (filled == (RGRADIENT)) {
00520         gid = svg_rgradstyle(job, A, 2);
00521     }
00522     gvputs(job, "<ellipse");
00523     svg_grstyle(job, filled, gid);
00524     gvprintf(job, " cx=\"%g\" cy=\"%g\"", A[0].x, -A[0].y);
00525     gvprintf(job, " rx=\"%g\" ry=\"%g\"",
00526              A[1].x - A[0].x, A[1].y - A[0].y);
00527     gvputs(job, "/>\n");
00528 }
00529 
00530 static void
00531 svg_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
00532            int arrow_at_end, int filled)
00533 {
00534   int gid = 0;
00535   
00536     if (filled == GRADIENT) {
00537         gid = svg_gradstyle(job, A, n);
00538     } else if (filled == (RGRADIENT)) {
00539         gid = svg_rgradstyle(job, A, n);
00540     }
00541     gvputs(job, "<path");
00542     svg_grstyle(job, filled, gid);
00543     gvputs(job, " d=\"");
00544     svg_bzptarray(job, A, n);
00545     gvputs(job, "\"/>\n");
00546 }
00547 
00548 static void svg_polygon(GVJ_t * job, pointf * A, int n, int filled)
00549 {
00550     int i, gid = 0;
00551     if (filled == GRADIENT) {
00552         gid = svg_gradstyle(job, A, n);
00553     } else if (filled == (RGRADIENT)) {
00554         gid = svg_rgradstyle(job, A, n);
00555     }
00556     gvputs(job, "<polygon");
00557     svg_grstyle(job, filled, gid);
00558     gvputs(job, " points=\"");
00559     for (i = 0; i < n; i++)
00560         gvprintf(job, "%g,%g ", A[i].x, -A[i].y);
00561     gvprintf(job, "%g,%g", A[0].x, -A[0].y);    /* because Adobe SVG is broken */
00562     gvputs(job, "\"/>\n");
00563 }
00564 
00565 static void svg_polyline(GVJ_t * job, pointf * A, int n)
00566 {
00567     int i;
00568 
00569     gvputs(job, "<polyline");
00570     svg_grstyle(job, 0, 0);
00571     gvputs(job, " points=\"");
00572     for (i = 0; i < n; i++)
00573         gvprintf(job, "%g,%g ", A[i].x, -A[i].y);
00574     gvputs(job, "\"/>\n");
00575 }
00576 
00577 /* color names from http://www.w3.org/TR/SVG/types.html */
00578 /* NB.  List must be LANG_C sorted */
00579 static char *svg_knowncolors[] = {
00580     "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
00581     "beige", "bisque", "black", "blanchedalmond", "blue",
00582     "blueviolet", "brown", "burlywood",
00583     "cadetblue", "chartreuse", "chocolate", "coral",
00584     "cornflowerblue", "cornsilk", "crimson", "cyan",
00585     "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
00586     "darkgreen", "darkgrey", "darkkhaki", "darkmagenta",
00587     "darkolivegreen", "darkorange", "darkorchid", "darkred",
00588     "darksalmon", "darkseagreen", "darkslateblue", "darkslategray",
00589     "darkslategrey", "darkturquoise", "darkviolet", "deeppink",
00590     "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
00591     "firebrick", "floralwhite", "forestgreen", "fuchsia",
00592     "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
00593     "green", "greenyellow", "grey",
00594     "honeydew", "hotpink", "indianred",
00595     "indigo", "ivory", "khaki",
00596     "lavender", "lavenderblush", "lawngreen", "lemonchiffon",
00597     "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
00598     "lightgray", "lightgreen", "lightgrey", "lightpink",
00599     "lightsalmon", "lightseagreen", "lightskyblue",
00600     "lightslategray", "lightslategrey", "lightsteelblue",
00601     "lightyellow", "lime", "limegreen", "linen",
00602     "magenta", "maroon", "mediumaquamarine", "mediumblue",
00603     "mediumorchid", "mediumpurple", "mediumseagreen",
00604     "mediumslateblue", "mediumspringgreen", "mediumturquoise",
00605     "mediumvioletred", "midnightblue", "mintcream",
00606     "mistyrose", "moccasin",
00607     "navajowhite", "navy", "oldlace",
00608     "olive", "olivedrab", "orange", "orangered", "orchid",
00609     "palegoldenrod", "palegreen", "paleturquoise",
00610     "palevioletred", "papayawhip", "peachpuff", "peru", "pink",
00611     "plum", "powderblue", "purple",
00612     "red", "rosybrown", "royalblue",
00613     "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell",
00614     "sienna", "silver", "skyblue", "slateblue", "slategray",
00615     "slategrey", "snow", "springgreen", "steelblue",
00616     "tan", "teal", "thistle", "tomato", "turquoise",
00617     "violet",
00618     "wheat", "white", "whitesmoke",
00619     "yellow", "yellowgreen"
00620 };
00621 
00622 gvrender_engine_t svg_engine = {
00623     svg_begin_job,
00624     0,                          /* svg_end_job */
00625     svg_begin_graph,
00626     svg_end_graph,
00627     svg_begin_layer,
00628     svg_end_layer,
00629     svg_begin_page,
00630     svg_end_page,
00631     svg_begin_cluster,
00632     svg_end_cluster,
00633     0,                          /* svg_begin_nodes */
00634     0,                          /* svg_end_nodes */
00635     0,                          /* svg_begin_edges */
00636     0,                          /* svg_end_edges */
00637     svg_begin_node,
00638     svg_end_node,
00639     svg_begin_edge,
00640     svg_end_edge,
00641     svg_begin_anchor,
00642     svg_end_anchor,
00643     0,                          /* svg_begin_anchor */
00644     0,                          /* svg_end_anchor */
00645     svg_textpara,
00646     0,                          /* svg_resolve_color */
00647     svg_ellipse,
00648     svg_polygon,
00649     svg_bezier,
00650     svg_polyline,
00651     svg_comment,
00652     0,                          /* svg_library_shape */
00653 };
00654 
00655 gvrender_features_t render_features_svg = {
00656     GVRENDER_Y_GOES_DOWN | GVRENDER_DOES_TRANSFORM | GVRENDER_DOES_LABELS | GVRENDER_DOES_MAPS | GVRENDER_DOES_TARGETS | GVRENDER_DOES_TOOLTIPS,        /* flags */
00657     4.,                         /* default pad - graph units */
00658     svg_knowncolors,            /* knowncolors */
00659     sizeof(svg_knowncolors) / sizeof(char *),   /* sizeof knowncolors */
00660     RGBA_BYTE,                  /* color_type */
00661 };
00662 
00663 gvdevice_features_t device_features_svg = {
00664     GVDEVICE_DOES_TRUECOLOR,    /* flags */
00665     {0., 0.},                   /* default margin - points */
00666     {0., 0.},                   /* default page width, height - points */
00667     {72., 72.},                 /* default dpi */
00668 };
00669 
00670 gvdevice_features_t device_features_svgz = {
00671     GVDEVICE_BINARY_FORMAT | GVDEVICE_COMPRESSED_FORMAT | GVDEVICE_DOES_TRUECOLOR,      /* flags */
00672     {0., 0.},                   /* default margin - points */
00673     {0., 0.},                   /* default page width, height - points */
00674     {72., 72.},                 /* default dpi */
00675 };
00676 
00677 gvplugin_installed_t gvrender_svg_types[] = {
00678     {FORMAT_SVG, "svg", 1, &svg_engine, &render_features_svg},
00679     {0, NULL, 0, NULL, NULL}
00680 };
00681 
00682 gvplugin_installed_t gvdevice_svg_types[] = {
00683     {FORMAT_SVG, "svg:svg", 1, NULL, &device_features_svg},
00684 #if HAVE_LIBZ
00685     {FORMAT_SVGZ, "svgz:svg", 1, NULL, &device_features_svgz},
00686 #endif
00687     {0, NULL, 0, NULL, NULL}
00688 };