/*
This software may only be used by you under license from AT&T Corp.
("AT&T"). A copy of AT&T's Source Code Agreement is available at
AT&T's Internet website having the URL:
<http://www.research.att.com/sw/tools/graphviz/license/source.html>
If you received this software without first entering into a license
with AT&T, you have an infringing copy of this software and cannot use
it without violating AT&T's intellectual property rights.
*/
#pragma prototyped
/* this is a rough start at a working SVG driver.
you can get image nodes by setting a node's
[shape=webimage,shapefile="http://www.your.site.com/path/image.png"]
(of course can also be a file: reference if you run locally.
which causes a warning (FIX that) but the back end turns this into
a custom shape. you can also set
[ URL = "http://some.place.com/whatever" ]
to get clickable nodes (and edges for that matter).
some major areas needing work:
0. fonts including embedded font support. is SVG finished in this area?
1. styles, including dotted/dashed lines, also "style function"
passthrough in SVG similar to what we have in postscript.
2. look at what happens in landscape mode, pagination? etc.
3. allow arbitrary user transforms via graph "style" attribute.
4. javascript hooks. particularly, look at this in the context
of SVG animation for dynadag output (n.b. dynadag only in alpha release)
5. image node improvement, e.g. set clip path to node boundary, and
support scaling with fixed aspect ratio (this feature seems to be
broken in the current Adobe SVG plugin for Windows).
6. can we support arbitrary HTML for node contents?
7. encode abstract graph as interleaved XML in some sort of graph XML dialect?
8. accessibility features
9. embed directions about getting plugin, if not present in browser
Stephen North
north@research.att.com
*/
#include <stdarg.h>
#include "render.h"
#include "utils.h"
#ifdef HAVE_LIBZ
#include "zlib.h"
#ifdef MSWIN32
#include <io.h>
#endif
#endif
/* SVG font modifiers */
#define REGULAR 0
#define BOLD 1
#define ITALIC 2
/* SVG patterns */
#define P_SOLID 0
#define P_NONE 15
#define P_DOTTED 4 /* i wasn't sure about this */
#define P_DASHED 11 /* or this */
/* SVG bold line constant */
#define WIDTH_NORMAL 1
#define WIDTH_BOLD 3
#ifdef TESTFAILED
/* sodipodi doesn't understand style sheets */
#define DEFAULT_STYLE\
"<style type='text/css'>\n" \
"<![CDATA[\n" \
".node ellipse {fill:none;filter:URL(#MyFilter)}\n" \
".node polygon {fill:none;filter:URL(#MyFilter)}\n" \
".cluster polygon {fill:none;filter:URL(#MyFilter)}\n" \
".edge path {fill:none;stroke:black;stroke-width:1;}\n" \
"text {font-family:Times;stroke:black;stroke-width:.4;font-size:12px}\n" \
"]]>\n" \
"</style>"
#define DEFAULT_FILTER \
"<filter id=\"MyFilter\" x=\"-20%\" y=\"-20%\" width=\"160%\" height=\"160%\">\n" \
"<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"4\" result=\"blur\"/>\n" \
"<feOffset in=\"blur\" dx=\"4\" dy=\"4\" result=\"offsetBlur\"/>\n" \
"<feSpecularLighting in=\"blur\" surfaceScale=\"5\" specularConstant=\"1\"\n" \
"specularExponent=\"10\" style=\"lighting-color:white\" result=\"specOut\">\n" \
"<fePointLight x=\"-5000\" y=\"-10000\" z=\"20000\"/>\n" \
"</feSpecularLighting>\n" \
"<feComposite in=\"specOut\" in2=\"SourceAlpha\" operator=\"in\" result=\"specOut\"/>\n" \
"<feComposite in=\"SourceGraphic\" in2=\"specOut\" operator=\"arithmetic\"\n" \
"k1=\"0\" k2=\"1\" k3=\"1\" k4=\"0\" result=\"litPaint\"/>\n" \
"<feMerge>\n" \
"<feMergeNode in=\"offsetBlur\"/>\n" \
"<feMergeNode in=\"litPaint\"/>\n" \
"</feMerge>\n" \
"</filter>"
#endif
#define SCALE 1
/* SVG dash array */
static char * sdarray = "5,2";
/* SVG dot array */
static char * sdotarray = "1,5";
static int N_pages;
static point Pages;
static double Scale;
static pointf Offset;
static int Rot;
static box PB;
static int onetime = TRUE;
static node_t *Curnode;
static int GraphURL, ClusterURL, NodeURL, EdgeURL;
typedef struct context_t {
char *pencolor,*fillcolor,*fontfam,fontopt,font_was_set;
char pen,fill,penwidth,style_was_set;
double fontsz;
} context_t;
#define MAXNEST 4
static context_t cstk[MAXNEST];
static int SP;
static char *op[] = {"graph", "node", "edge", "graph"};
#ifdef HAVE_LIBZ
static gzFile Zfile;
#endif
static int svg_fputs (char *s)
{
int len;
len = strlen(s);
#ifdef HAVE_LIBZ
if (Output_lang == SVGZ)
return gzwrite(Zfile,s,(unsigned)len);
else
#endif
return fwrite(s,sizeof(char),(unsigned)len,Output_file);
}
/* svg_printf:
* Note that this function is unsafe due to the fixed buffer size.
* It should only be used when the caller is sure the input will not
* overflow the buffer. In particular, it should be avoided for
* input coming from users. Also, if vsnprintf is available, the
* code should check for return values to use it safely.
*/
static int
svg_printf(const char *format, ...)
{
char buf[BUFSIZ];
va_list argp;
int len;
va_start(argp, format);
#ifdef HAVE_VSNPRINTF
(void)vsnprintf(buf, sizeof(buf), format, argp);
#else
(void)vsprintf(buf, format, argp);
#endif
va_end(argp);
len = strlen(buf); /* some *sprintf (e.g C99 std)
don't return the number of
bytes actually written */
#ifdef HAVE_LIBZ
if (Output_lang == SVGZ)
return gzwrite(Zfile,buf,(unsigned)len);
else
#endif
return fwrite(buf,sizeof(char),(unsigned)len,Output_file);
}
static void
svg_reset(void)
{
onetime = TRUE;
}
static void
init_svg(void)
{
SP = 0;
cstk[0].pencolor = DEFAULT_COLOR; /* SVG pencolor */
cstk[0].fillcolor = ""; /* SVG fillcolor */
cstk[0].fontfam = DEFAULT_FONTNAME; /* font family name */
cstk[0].fontsz = DEFAULT_FONTSIZE; /* font size */
cstk[0].fontopt = REGULAR; /* modifier: REGULAR, BOLD or ITALIC */
cstk[0].pen = P_SOLID; /* pen pattern style, default is solid */
cstk[0].fill = P_NONE;
cstk[0].penwidth = WIDTH_NORMAL;
}
/* can't hack a transform directly in SVG preamble, alas */
static point
svgpt(point p)
{
point rv;
if (Rot == 0) {
rv.x = PB.LL.x / Scale + p.x + Offset.x;
rv.y = PB.UR.y / Scale - 1 - p.y - Offset.y;
} else {
rv.x = PB.UR.x / Scale - 1 - p.y - Offset.x;
rv.y = PB.UR.y / Scale - 1 - p.x - Offset.y;
}
return rv;
}
static void
svgbzptarray(point* A, int n)
{
int i;
point p;
char *c;
c = "M"; /* first point */
for (i = 0; i < n; i++) {
p.x = A[i].x; p.y = A[i].y;
p = svgpt(p);
svg_printf("%s%d,%d",c,p.x,p.y);
if (i==0) c = "C"; /* second point */
else c = " "; /* remaining points */
}
}
static char*
svg_resolve_color(char *name)
{
/* color names from http://www.w3.org/TR/SVG/types.html */
static char *known_colors[] = {
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
"beige", "bisque", "black", "blanchedalmond", "blue",
"blueviolet", "brown", "burlywood",
"cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan",
"darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen",
"darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon",
"darkseagreen", "darkslateblue", "darkslategray",
"darkslategrey", "darkturquoise", "darkviolet", "deeppink",
"deepskyblue", "dimgray", "dimgrey", "dodgerblue",
"firebrick", "floralwhite", "forestgreen", "fuchsia",
"gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green",
"greenyellow", "grey",
"honeydew", "hotpink", "indianred",
"indigo", "ivory", "khaki",
"lavender", "lavenderblush", "lawngreen", "lemonchiffon",
"lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
"lightgray", "lightgreen", "lightgrey", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue",
"lightslategray", "lightslategrey", "lightsteelblue",
"lightyellow", "lime", "limegreen", "linen",
"magenta", "maroon", "mediumaquamarine", "mediumblue",
"mediumorchid", "mediumpurple", "mediumseagreen",
"mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream",
"mistyrose", "moccasin",
"navajowhite", "navy", "oldlace",
"olive", "olivedrab", "orange", "orangered", "orchid",
"palegoldenrod", "palegreen", "paleturquoise",
"palevioletred", "papayawhip", "peachpuff", "peru", "pink",
"plum", "powderblue", "purple",
"red", "rosybrown", "royalblue",
"saddlebrown", "salmon", "sandybrown", "seagreen", "seashell",
"sienna", "silver", "skyblue", "slateblue", "slategray",
"slategrey", "snow", "springgreen", "steelblue",
"tan", "teal", "thistle", "tomato", "turquoise",
"violet",
"wheat", "white", "whitesmoke",
"yellow", "yellowgreen",
0};
static char buf[SMALLBUF];
char *tok, **known;
color_t color;
tok = canontoken(name);
for (known = known_colors; *known; known++)
if (streq(tok,*known)) break;
if (*known == 0) {
if (streq(tok,"transparent")) {
tok = "none";
}
else {
colorxlate(name,&color,RGBA_BYTE);
sprintf(buf,"#%02x%02x%02x",
color.u.rgba[0],
color.u.rgba[1],
color.u.rgba[2]);
tok = buf;
}
}
return tok;
}
static void
svg_font(context_t* cp)
{
char *color, buf[BUFSIZ];
int needstyle=0;
strcpy(buf," style=\"");
if (strcasecmp(cp->fontfam,DEFAULT_FONTNAME)) {
sprintf(buf+strlen(buf),"font-family:%s;",cp->fontfam);
needstyle++;
}
if (cp->fontsz != DEFAULT_FONTSIZE) {
sprintf(buf+strlen(buf),"font-size:%.2f;",(cp->fontsz));
needstyle++;
}
color = svg_resolve_color(cp->pencolor);
if ((strcasecmp(color,"black"))) {
sprintf(buf+strlen(buf),"fill:%s;",color);
needstyle++;
}
if (needstyle) {
strcat(buf,"\"");
svg_fputs(buf);
}
}
static void
svg_grstyle(context_t* cp, int filled)
{
svg_printf(" style=\"");
if (filled)
svg_printf("fill:%s;",svg_resolve_color(cp->fillcolor));
else
svg_printf("fill:none;");
svg_printf("stroke:%s;",svg_resolve_color(cp->pencolor));
if (cp->penwidth!=WIDTH_NORMAL)
svg_printf("stroke-width:%d;",cp->penwidth);
if( cp->pen == P_DASHED ) {
svg_printf("stroke-dasharray:%s;", sdarray);
} else if( cp->pen == P_DOTTED) {
svg_printf("stroke-dasharray:%s;", sdotarray);
}
svg_printf("\"");
}
static void
svg_comment(void* obj, attrsym_t* sym)
{
char *str = late_string(obj,sym,"");
if (str[0]) {
svg_fputs ("<!-- ");
/* FIXME - should check for --> sequences in str */
svg_fputs (str);
svg_fputs (" -->\n");
}
}
static void
svg_begin_job(FILE *ofp, graph_t *g, char **lib, char *user, char *info[], point pages)
{
char *s;
#if HAVE_LIBZ
int fd;
#endif
if (Output_lang == SVGZ) {
#if HAVE_LIBZ
fd = dup(fileno(Output_file)); /* open dup so can gzclose
independent of FILE close */
#ifdef HAVE_SETMODE
#ifdef O_BINARY
/*
* Windows will do \n -> \r\n translations on
* stdout unless told otherwise.
*/
setmode(fd, O_BINARY);
#endif
#endif
Zfile = gzdopen(fd, "wb");
if (!Zfile) {
agerr(AGERR, "Error opening compressed output file\n");
exit(1);
}
#else
agerr(AGERR, "No support for compressed output. Not compiled with zlib.\n");
exit(1);
#endif
}
Pages = pages;
N_pages = pages.x * pages.y;
svg_printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
if ((s = agget(g, "stylesheet")) && strlen(s)) {
svg_printf("<?xml-stylesheet href=\"%s\" type=\"text/css\"?>\n", s);
}
svg_printf("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n");
svg_printf(" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"");
#if 1
/* This is to work around a bug in the SVG 1.0 DTD */
if ((agfindattr(g,"URL")
|| agfindattr(g->proto->n,"URL")
|| agfindattr(g->proto->e,"URL"))) {
svg_printf(" [\n <!ATTLIST svg xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\">\n]");
}
#endif
svg_fputs(">\n");
svg_printf("<!-- Generated by %s version %s (%s)\n",
info[0], info[1], info[2]);
svg_printf(" For user: %s Title: %s Pages: %d -->\n",
user, g->name, N_pages);
}
static void
svg_end_job(void)
{
}
static void
svg_begin_graph(graph_t* g, box bb, point pb)
{
char *str;
double res;
PB.LL.x = PB.LL.y = 0;
PB.UR.x = (bb.UR.x - bb.LL.x + 2*GD_drawing(g)->margin.x) * SCALE;
PB.UR.y = (bb.UR.y - bb.LL.y + 2*GD_drawing(g)->margin.y) * SCALE;
Offset.x = GD_drawing(g)->margin.x * SCALE;
Offset.y = GD_drawing(g)->margin.y * SCALE;
if (onetime) {
#if 0
fprintf(stderr,"LL %d %d UR %d %d\n", PB.LL.x,PB.LL.y,PB.UR.x, PB.UR.y);
#endif
init_svg();
svg_comment(g,agfindattr(g,"comment"));
onetime = FALSE;
}
if ((str = agget(g,"resolution")) && str[0]) res = atof(str);
else res = 96; /* terrific guess */
if (res < 1.0)
svg_printf("<svg width=\"%dpt\" height=\"%dpt\"\n",
PB.UR.x - PB.LL.x + 2, PB.UR.y - PB.LL.y + 2);
else svg_printf("<svg width=\"%dpx\" height=\"%dpx\"\n",
ROUND((res/POINTS_PER_INCH)*(PB.UR.x - PB.LL.x)) + 2,ROUND((res/POINTS_PER_INCH)*(PB.UR.y - PB.LL.y) + 2));
/* establish absolute units in points */
svg_printf(" viewBox = \"%d %d %d %d\"\n", PB.LL.x - 1, PB.LL.y - 1,
PB.UR.x + 1, PB.UR.y + 1);
/* namespace of svg */
svg_fputs(" xmlns=\"http://www.w3.org/2000/svg\"");
/* namespace of xlink if needed */
if ((agfindattr(g,"URL")
|| agfindattr(g->proto->n,"URL")
|| agfindattr(g->proto->e,"URL"))) {
svg_fputs(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
}
svg_fputs(">\n");
}
static void
svg_end_graph(void)
{
#ifdef HAVE_LIBZ
if (Output_lang==SVGZ) gzclose(Zfile);
#endif
}
static void svg_output_anchor(char *url, char *label, char *tooltip) {
svg_printf("<a xlink:href=\"%s\"",xml_string(url));
#if 0
/* apparently there is no alt tag in svg anchors ??? */
if (label && label[0]) {
svg_printf(" xlink:alt=\"%s\"", xml_string(label));
}
#endif
if (tooltip && tooltip[0]) {
svg_printf(" xlink:title=\"%s\"", xml_string(tooltip));
}
svg_fputs(">\n");
}
static void
svg_begin_page(graph_t *g, point page, double scale, int rot, point offset)
{
int page_number;
char *s;
/* point sz; */
Scale = scale;
Rot = rot;
page_number = page.x + page.y * Pages.x + 1;
/* sz = sub_points(PB.UR,PB.LL); */
/* its really just a page of the graph, but its still a graph,
* and it is the entire graph if we're not currently paging */
svg_printf("<g id=\"%s0\" class=\"graph\"", op[Obj]);
if (scale != 1.0) svg_printf(" transform = \"scale(%f)\"\n",scale);
/* default style */
svg_printf(" style=\"font-family:%s;font-size:%.2f;\">\n",
cstk[0].fontfam,cstk[0].fontsz);
svg_printf("<title>%s</title>\n", xml_string(g->name));
if ((s = agget(g, "URL")) && strlen(s)) {
GraphURL = 1;
s = strdup_and_subst_graph(s,g);
svg_printf("<a xlink:href=\"%s\">\n",xml_string(s));
free(s);
} else {
GraphURL = 0;
}
}
static void
svg_end_page(void)
{
if (GraphURL) {
svg_printf("</a>");
ClusterURL = 0;
}
svg_fputs("</g></svg>\n");
}
static void
svg_begin_cluster(graph_t* g)
{
char *s;
svg_printf("<g id=\"%s%ld\" class=\"cluster\">",op[Obj],g->meta_node->id);
svg_printf("<title>%s</title>\n",xml_string(g->name));
if ((s = agget(g, "URL")) && strlen(s)) {
ClusterURL = 1;
s = strdup_and_subst_graph(s,g);
svg_printf("<a xlink:href=\"%s\">\n",xml_string(s));
free(s);
} else {
ClusterURL = 0;
}
}
static void
svg_end_cluster (void)
{
if (ClusterURL) {
svg_printf("</a>");
ClusterURL = 0;
}
svg_printf("</g>\n");
}
static void svg_begin_nodes(void) { }
static void svg_end_nodes(void) { }
static void svg_begin_edges(void) { }
static void svg_end_edges(void) { }
static void
svg_begin_node(node_t* n)
{
char *url,*label,*tooltip, *m_tooltip=NULL;
Curnode = n;
#if 0
svg_printf("<!-- %s -->\n",n->name);
svg_comment(n,N_comment);
#endif
svg_printf("<g id=\"%s%ld\" class=\"node\">",op[Obj],n->id);
svg_printf("<title>%s</title>\n",xml_string(n->name));
if ((url = agget(n, "URL")) && strlen(url)) {
NodeURL = 1;
url = strdup_and_subst_node(url,n);
label = ND_label(n)->text;
if ((tooltip = agget(n, "tooltip")) && strlen(tooltip)) {
m_tooltip = tooltip = strdup_and_subst_node(tooltip,n);
}
else {
tooltip = label;
}
svg_output_anchor(url, label, tooltip);
if (m_tooltip)
free(tooltip);
free(url);
} else {
NodeURL = 0;
}
}
static void
svg_end_node (void)
{
if (NodeURL) {
svg_printf("</a>");
NodeURL = 0;
}
svg_printf("</g>\n");
}
static void
svg_begin_edge (edge_t* e)
{
char *url,*label=NULL,*tooltip,*m_tooltip=NULL,*edgeop;
textlabel_t *lab=NULL;
svg_printf("<g id=\"%s%ld\" class=\"edge\">",op[Obj],e->id);
if (e->tail->graph->root->kind & AGFLAG_DIRECTED)
edgeop = "->";
else
edgeop = "--";
svg_printf("<title>%s%s",xml_string(e->tail->name),edgeop);
/* can't do this in single svg_printf because
* xml_string's buffer gets reused. */
svg_printf("%s</title>\n",xml_string(e->head->name));
if ((url = agget(e, "URL")) && strlen(url)) {
EdgeURL = 1;
url = strdup_and_subst_edge(url,e);
if ((lab=ED_label(e))) {
label = lab->text;
}
if ((tooltip = agget(e, "tooltip")) && strlen(tooltip)) {
m_tooltip = tooltip = strdup_and_subst_edge(tooltip,e);
}
else {
tooltip = label;
}
svg_output_anchor(url, label, tooltip);
if (m_tooltip)
free(tooltip);
free(url);
} else {
EdgeURL = 0;
}
}
static void
svg_end_edge (void)
{
if (EdgeURL) {
svg_printf("</a>");
EdgeURL = 0;
}
svg_printf("</g>\n");
}
static void
svg_begin_context(void)
{
assert(SP + 1 < MAXNEST);
cstk[SP+1] = cstk[SP];
SP++;
}
static void
svg_end_context(void)
{
int psp = SP - 1;
assert(SP > 0);
/*free(cstk[psp].fontfam);*/
SP = psp;
}
static void
svg_set_font(char* name, double size)
{
char *p;
#if 0
/* see below */
char *q;
#endif
context_t *cp;
cp = &(cstk[SP]);
cp->font_was_set = TRUE;
cp->fontsz = size;
p = strdup(name);
#if 0
/*
* this doesn't work as originally intended
* fontnames can be things like: "Times-Roman"
* where "Roman" is not an ITALIC or BOLD indicator.
*/
if ((q = strchr(p,'-'))) {
*q++ = 0;
if (strcasecmp(q,"italic") == 0)
cp->fontopt = ITALIC;
else if (strcasecmp(q,"bold") == 0)
cp->fontopt = BOLD;
}
cp->fontfam = p;
#else
cp->fontfam = p;
#endif
}
static void
svg_set_pencolor(char* name)
{
cstk[SP].pencolor = name;
}
static void
svg_set_fillcolor(char* name)
{
cstk[SP].fillcolor = name;
}
static void
svg_set_style(char** s)
{
char *line, *p;
context_t *cp;
cp = &(cstk[SP]);
while ((p = line = *s++)) {
if (streq(line,"solid")) cp->pen = P_SOLID;
else if (streq(line,"dashed")) cp->pen = P_DASHED;
else if (streq(line,"dotted")) cp->pen = P_DOTTED;
else if (streq(line,"invis")) cp->pen = P_NONE;
else if (streq(line,"bold")) cp->penwidth = WIDTH_BOLD;
else if (streq(line, "setlinewidth")) {
while (*p) p++; p++;
cp->penwidth = atol(p);
}
else if (streq(line,"filled")) cp->fill = P_SOLID;
else if (streq(line,"unfilled")) cp->fill = P_NONE;
else {
agerr(AGERR, "svg_set_style: unsupported style %s - ignoring\n",
line);
}
cp->style_was_set = TRUE;
}
/* if (cp->style_was_set) svg_style(cp); */
}
static void
svg_textline(point p, textline_t *line)
{
char *anchor, *string;
point mp;
context_t *cp;
string = xml_string(line->str);
if (strlen(string) == 0) {
/* its zero length, don't draw */
return;
}
cp = &(cstk[SP]);
if( cp->pen == P_NONE ) {
/* its invisible, don't draw */
return;
}
switch(line->just) {
case 'l':
anchor="start";
break;
case 'r':
anchor="end";
break;
default:
case 'n':
anchor="middle";
break;
}
mp = svgpt(p);
svg_printf("<text text-anchor=\"%s\" ",anchor);
if (Rot) {
svg_printf("transform=\"rotate(-90 %d %d)\" ",mp.x,mp.y);
}
svg_printf("x=\"%d\" y=\"%d\"",mp.x,mp.y);
svg_font(cp);
svg_fputs(">");
svg_fputs(string);
svg_fputs("</text>\n");
}
static void
svg_ellipse(point p, int rx, int ry, int filled)
{
point mp;
if( cstk[SP].pen == P_NONE ) {
/* its invisible, don't draw */
return;
}
mp.x = p.x;
mp.y = p.y;
mp = svgpt(mp);
svg_printf("<ellipse cx=\"%d\" cy=\"%d\"",
mp.x,mp.y);
if (Rot) {int t; t = rx; rx = ry; ry = t;}
mp.x = rx;
mp.y = ry;
svg_printf(" rx=\"%d\" ry=\"%d\"",mp.x,mp.y);
svg_grstyle(&cstk[SP], filled);
svg_printf("/>\n");
}
static void
svg_bezier(point* A, int n, int arrow_at_start, int arrow_at_end)
{
if( cstk[SP].pen == P_NONE ) {
/* its invisible, don't draw */
return;
}
svg_fputs("<path");
svg_grstyle(&cstk[SP],0);
svg_fputs(" d=\"");
svgbzptarray(A,n);
svg_fputs("\"/>\n");
}
static void
svg_polygon(point *A, int n, int filled)
{
int i;
point p;
if( cstk[SP].pen == P_NONE ) {
/* its invisible, don't draw */
return;
}
svg_printf("<polygon");
svg_grstyle(&cstk[SP],filled);
svg_printf(" points=\"");
for (i = 0; i < n; i++) {
p = svgpt(A[i]);
svg_printf("%d,%d ",p.x,p.y);
}
/* because Adobe SVG is broken */
p = svgpt(A[0]);
svg_printf("%d,%d",p.x,p.y);
svg_printf("\"/>\n");
}
static void
svg_polyline(point* A, int n)
{
int i;
point p;
if( cstk[SP].pen == P_NONE ) {
/* its invisible, don't draw */
return;
}
svg_printf("<polyline");
svg_grstyle(&cstk[SP],0);
svg_printf(" points=\"");
for (i = 0; i < n; i++) {
p = svgpt(A[i]);
svg_printf("%d,%d ",p.x,p.y);
}
svg_printf("\"/>\n");
}
static void
svg_user_shape(char *name, point *A, int n, int filled)
{
int i;
point p;
point sz;
char *imagefile;
int minx, miny;
if( cstk[SP].pen == P_NONE ) {
/* its invisible, don't draw */
return;
}
imagefile = agget(Curnode,"shapefile");
if (imagefile == 0) {
svg_polygon(A, n, filled);
return;
}
p = ND_coord_i(Curnode);
p.x -= ND_lw_i(Curnode);
p.y += ND_ht_i(Curnode)/2;
p = svgpt(p);
sz.x = ROUND((ND_lw_i(Curnode)+ND_rw_i(Curnode)));
sz.y = ROUND(ND_ht_i(Curnode));
svg_printf("<clipPath id=\"mypath%s%s\">\n", name, Curnode->name);
svg_printf("<polygon points=\"");
minx = svgpt(A[0]).x;
miny = svgpt(A[0]).y;
for (i = 0; i < n; i++) {
p = svgpt(A[i]);
if(p.x < minx)
minx = p.x;
if(p.y < miny)
miny = p.y;
svg_printf("%d,%d ",p.x,p.y);
}
/* because Adobe SVG is broken (?) */
p = svgpt(A[0]);
svg_printf("%d,%d ",p.x,p.y);
svg_printf("\"/>\n</clipPath>\n");
svg_printf("<image xlink:href=\"%s\" width=\"%dpx\" height=\"%dpx\" preserveAspectRatio=\"xMidYMid meet\" x=\"%d\" y=\"%d\" clip-path=\"url(#mypath%s%s)\"/>\n",
imagefile, sz.x, sz.y, minx, miny, name, Curnode->name);
}
codegen_t SVG_CodeGen = {
svg_reset,
svg_begin_job, svg_end_job,
svg_begin_graph, svg_end_graph,
svg_begin_page, svg_end_page,
svg_begin_cluster, svg_end_cluster,
svg_begin_nodes, svg_end_nodes,
svg_begin_edges, svg_end_edges,
svg_begin_node, svg_end_node,
svg_begin_edge, svg_end_edge,
svg_begin_context, svg_end_context,
svg_set_font, svg_textline,
svg_set_pencolor, svg_set_fillcolor, svg_set_style,
svg_ellipse, svg_polygon,
svg_bezier, svg_polyline,
0 /* svg_arrowhead */, svg_user_shape,
0 /* svg_comment */, 0 /* svg_textsize */
};
|