#pragma prototyped
#include <signal.h>
#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "macros.h"
#include "const.h"
#include "types.h"
#include "graph.h"
#include "globals.h"
#include "utils.h"
#include "renderprocs.h"
#include "gvre.h"
#ifdef HAVE_LIBZ
#include "zlib.h"
#ifdef MSWIN32
#include <io.h>
#endif
#endif
#define REGULAR 0
#define BOLD 1
#define ITALIC 2
#define P_SOLID 0
#define P_NONE 15
#define P_DOTTED 4
#define P_DASHED 11
#define WIDTH_NORMAL 1
#define WIDTH_BOLD 3
#ifdef TESTFAILED
#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
static char *sdarray = "5,2";
static char *sdotarray = "1,5";
static int N_pages;
static pointf Offset;
static box PB;
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;
#ifdef HAVE_LIBZ
static gzFile Zfile;
#endif
static int svg_fputs(GVC_t *gvc, 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, gvc->job->output_file);
}
static int svg_printf(GVC_t *gvc, 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);
#ifdef HAVE_LIBZ
if (Output_lang == SVGZ)
return gzwrite(Zfile, buf, (unsigned) len);
else
#endif
return fwrite(buf, sizeof(char), (unsigned) len, gvc->job->output_file);
}
static void init_svg(void)
{
SP = 0;
cstk[0].pencolor = DEFAULT_COLOR;
cstk[0].fillcolor = "";
cstk[0].fontfam = DEFAULT_FONTNAME;
cstk[0].fontsz = DEFAULT_FONTSIZE;
cstk[0].fontopt = REGULAR;
cstk[0].pen = P_SOLID;
cstk[0].fill = P_NONE;
cstk[0].penwidth = WIDTH_NORMAL;
}
static point svgpt(GVC_t *gvc, pointf p)
{
pointf rv;
point RV;
if (gvc->rot == 0) {
rv.x = PB.LL.x / gvc->scale + p.x + Offset.x;
rv.y = PB.UR.y / gvc->scale - 1 - p.y - Offset.y;
} else {
rv.x = PB.UR.x / gvc->scale - 1 - p.y - Offset.x;
rv.y = PB.UR.y / gvc->scale - 1 - p.x - Offset.y;
}
PF2P(rv, RV);
return RV;
}
static void svgbzptarray(GVC_t *gvc, pointf * A, int n)
{
int i;
point p;
char *c;
c = "M";
for (i = 0; i < n; i++) {
p = svgpt(gvc, A[i]);
svg_printf(gvc, "%s%d,%d", c, p.x, p.y);
if (i == 0)
c = "C";
else
c = " ";
}
}
static char *svg_resolve_color(char *name)
{
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(GVC_t *gvc, 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(gvc, buf);
}
}
static void svg_grstyle(GVC_t *gvc, context_t * cp, int filled)
{
svg_fputs(gvc, " style=\"");
if (filled)
svg_printf(gvc, "fill:%s;", svg_resolve_color(cp->fillcolor));
else
svg_fputs(gvc, "fill:none;");
svg_printf(gvc, "stroke:%s;", svg_resolve_color(cp->pencolor));
if (cp->penwidth != WIDTH_NORMAL)
svg_printf(gvc, "stroke-width:%d;", cp->penwidth);
if (cp->pen == P_DASHED) {
svg_printf(gvc, "stroke-dasharray:%s;", sdarray);
} else if (cp->pen == P_DOTTED) {
svg_printf(gvc, "stroke-dasharray:%s;", sdotarray);
}
svg_fputs(gvc, "\"");
}
static void svg_comment(GVC_t * gvc, void *obj, attrsym_t * sym)
{
char *str = late_string(obj, sym, "");
if (str[0]) {
svg_fputs(gvc, "<!-- ");
svg_fputs(gvc, str);
svg_fputs(gvc, " -->\n");
}
}
static void svg_begin_job(GVC_t * gvc)
{
char *s;
#if HAVE_LIBZ
int fd;
#endif
graph_t *g = gvc->g;
if (Output_lang == SVGZ) {
#if HAVE_LIBZ
fd = dup(fileno(gvc->job->output_file));
#ifdef HAVE_SETMODE
#ifdef O_BINARY
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
}
N_pages = gvc->pages.x * gvc->pages.y;
svg_fputs
(gvc, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
if ((s = agget(g, "stylesheet")) && strlen(s)) {
svg_fputs(gvc, "<?xml-stylesheet href=\"");
svg_fputs(gvc, s);
svg_fputs(gvc, "\" type=\"text/css\"?>\n");
}
svg_fputs(gvc, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n");
svg_fputs
(gvc, " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"");
#if 1
if ((agfindattr(g, "URL")
|| agfindattr(g->proto->n, "URL")
|| agfindattr(g->proto->e, "URL"))) {
svg_fputs(gvc, " [\n <!ATTLIST svg xmlns:xlink");
svg_fputs(gvc, " CDATA #FIXED \"http://www.w3.org/1999/xlink\">\n]");
}
#endif
svg_fputs(gvc, ">\n<!-- Generated by ");
svg_fputs(gvc, gvc->info[0]);
svg_fputs(gvc, " version ");
svg_fputs(gvc, gvc->info[1]);
svg_fputs(gvc, " (");
svg_fputs(gvc, gvc->info[2]);
svg_fputs(gvc, ")\n For user: ");
svg_fputs(gvc, gvc->user);
svg_fputs(gvc, " Title: ");
svg_fputs(gvc, g->name);
svg_printf(gvc, " Pages: %d -->\n", N_pages);
}
static void svg_begin_graph(GVC_t * gvc)
{
char *str;
double res;
graph_t *g = gvc->g;
PB.LL.x = PB.LL.y = 0;
PB.UR.x = (gvc->bb.UR.x - gvc->bb.LL.x + 2 * GD_drawing(g)->margin.x) * SCALE;
PB.UR.y = (gvc->bb.UR.y - gvc->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 (gvc->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(gvc, g, agfindattr(g, "comment"));
}
if ((str = agget(g, "resolution")) && str[0])
res = atof(str);
else
res = 96;
if (res < 1.0)
svg_printf(gvc, "<svg width=\"%dpt\" height=\"%dpt\"\n",
PB.UR.x - PB.LL.x + 2, PB.UR.y - PB.LL.y + 2);
else
svg_printf(gvc, "<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));
svg_printf(gvc, " viewBox = \"%d %d %d %d\"\n", PB.LL.x - 1,
PB.LL.y - 1, PB.UR.x + 1, PB.UR.y + 1);
svg_fputs(gvc, " xmlns=\"http://www.w3.org/2000/svg\"");
if ((agfindattr(g, "URL")
|| agfindattr(g->proto->n, "URL")
|| agfindattr(g->proto->e, "URL"))) {
svg_fputs(gvc, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
}
svg_fputs(gvc, ">\n");
}
static void svg_end_graph(GVC_t * gvc)
{
svg_fputs(gvc, "</svg>\n");
#ifdef HAVE_LIBZ
if (Output_lang == SVGZ)
gzclose(Zfile);
#endif
}
static void svg_output_anchor(GVC_t *gvc, char *url, char *label, char *tooltip)
{
svg_fputs(gvc, "<a xlink:href=\"");
svg_fputs(gvc, xml_string(url));
svg_fputs(gvc, "\"");
if (tooltip && tooltip[0]) {
svg_fputs(gvc, " xlink:title=\"");
svg_fputs(gvc, xml_string(tooltip));
svg_fputs(gvc, "\"");
}
svg_fputs(gvc, ">\n");
}
static void svg_begin_page(GVC_t * gvc)
{
char *s;
char *kind = "graph";
graph_t *g = gvc->g;
svg_printf(gvc, "<g id=\"%s0\" class=\"%s\"", kind, kind);
if (gvc->scale != 1.0)
svg_printf(gvc, " transform = \"scale(%f)\"\n", gvc->scale);
svg_fputs(gvc, " style=\"font-family:");
svg_fputs(gvc, cstk[0].fontfam);
svg_printf(gvc, ";font-size:%.2f;\">\n", cstk[0].fontsz);
svg_fputs(gvc, "<title>");
svg_fputs(gvc, xml_string(g->name));
svg_fputs(gvc, "</title>\n");
if ((s = agget(g, "URL")) && strlen(s)) {
GraphURL = 1;
s = strdup_and_subst_graph(s, g);
svg_fputs(gvc, "<a xlink:href=\"");
svg_fputs(gvc, xml_string(s));
svg_fputs(gvc, "\">\n");
free(s);
} else {
GraphURL = 0;
}
}
static void svg_end_page(GVC_t * gvc)
{
if (GraphURL) {
svg_fputs(gvc, "</a>");
ClusterURL = 0;
}
svg_fputs(gvc, "</g>\n");
}
static void svg_begin_layer(GVC_t * gvc)
{
svg_fputs(gvc, "<g id=\"");
svg_fputs(gvc, xml_string(gvc->layerName));
svg_fputs(gvc, "\" class=\"layer\">\n");
Obj = NONE;
}
static void svg_end_layer(GVC_t * gvc)
{
svg_fputs(gvc, "</g>\n");
Obj = NONE;
}
static void svg_begin_cluster(GVC_t * gvc)
{
char *s;
graph_t *sg = gvc->sg;
char *kind = "cluster";
svg_printf(gvc, "<g id=\"%s%ld\" class=\"%s\">", kind,
sg->meta_node->id, kind);
svg_fputs(gvc, "<title>");
svg_fputs(gvc, xml_string(sg->name));
svg_fputs(gvc, "</title>\n");
if ((s = agget(sg, "URL")) && strlen(s)) {
ClusterURL = 1;
s = strdup_and_subst_graph(s, sg);
svg_fputs(gvc, "<a xlink:href=\"");
svg_fputs(gvc, xml_string(s));
svg_fputs(gvc, "\">\n");
free(s);
} else {
ClusterURL = 0;
}
}
static void svg_end_cluster(GVC_t * gvc)
{
if (ClusterURL) {
svg_fputs(gvc, "</a>");
ClusterURL = 0;
}
svg_fputs(gvc, "</g>\n");
}
static void svg_begin_node(GVC_t * gvc)
{
char *url, *label, *tooltip, *m_tooltip = NULL;
node_t *n = gvc->n;
char *kind = "node";
#if 0
svg_printf(gvc, "<!-- %s -->\n", n->name);
svg_comment(n, N_comment);
#endif
svg_printf(gvc, "<g id=\"%s%ld\" class=\"%s\">", kind, n->id, kind);
svg_fputs(gvc, "<title>");
svg_fputs(gvc, xml_string(n->name));
svg_fputs(gvc, "</title>\n");
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(gvc, url, label, tooltip);
if (m_tooltip)
free(tooltip);
free(url);
} else {
NodeURL = 0;
}
}
static void svg_end_node(GVC_t * gvc)
{
if (NodeURL) {
svg_fputs(gvc, "</a>");
NodeURL = 0;
}
svg_fputs(gvc, "</g>\n");
}
static void svg_begin_edge(GVC_t * gvc)
{
char *url, *label = NULL, *tooltip, *m_tooltip = NULL, *edgeop;
textlabel_t *lab = NULL;
edge_t *e = gvc->e;
char *kind = "edge";
svg_printf(gvc, "<g id=\"%s%ld\" class=\"edge\">", kind, e->id, kind);
if (e->tail->graph->root->kind & AGFLAG_DIRECTED)
edgeop = "->";
else
edgeop = "--";
svg_fputs(gvc, "<title>");
svg_fputs(gvc, xml_string(e->tail->name));
svg_fputs(gvc, edgeop);
svg_fputs(gvc, xml_string(e->head->name));
svg_fputs(gvc, "</title>\n");
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(gvc, url, label, tooltip);
if (m_tooltip)
free(tooltip);
free(url);
} else {
EdgeURL = 0;
}
}
static void svg_end_edge(GVC_t * gvc)
{
if (EdgeURL) {
svg_fputs(gvc, "</a>");
EdgeURL = 0;
}
svg_fputs(gvc, "</g>\n");
}
static void svg_begin_context(GVC_t * gvc)
{
assert(SP + 1 < MAXNEST);
cstk[SP + 1] = cstk[SP];
SP++;
}
static void svg_end_context(GVC_t * gvc)
{
int psp = SP - 1;
assert(SP > 0);
SP = psp;
}
static void svg_set_font(GVC_t * gvc, char *name, double size)
{
char *p;
#if 0
char *q;
#endif
context_t *cp;
cp = &(cstk[SP]);
cp->font_was_set = TRUE;
cp->fontsz = size;
p = strdup(name);
#if 0
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(GVC_t * gvc, char *name)
{
cstk[SP].pencolor = name;
}
static void svg_set_fillcolor(GVC_t * gvc, char *name)
{
cstk[SP].fillcolor = name;
}
static void svg_set_style(GVC_t * gvc, 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;
}
}
static void svg_textline(GVC_t * gvc, pointf p, textline_t * line)
{
char *anchor, *string;
point mp;
context_t *cp;
string = xml_string(line->str);
if (strlen(string) == 0) {
return;
}
cp = &(cstk[SP]);
if (cp->pen == P_NONE) {
return;
}
switch (line->just) {
case 'l':
anchor = "start";
break;
case 'r':
anchor = "end";
break;
default:
case 'n':
anchor = "middle";
break;
}
mp = svgpt(gvc, p);
svg_printf(gvc, "<text text-anchor=\"%s\" ", anchor);
if (gvc->rot) {
svg_printf(gvc, "transform=\"rotate(-90 %d %d)\" ", mp.x, mp.y);
}
svg_printf(gvc, "x=\"%d\" y=\"%d\"", mp.x, mp.y);
svg_font(gvc, cp);
svg_fputs(gvc, ">");
svg_fputs(gvc, string);
svg_fputs(gvc, "</text>\n");
}
static void svg_ellipse(GVC_t * gvc, pointf p, double rx, double ry, int filled)
{
point mp;
if (cstk[SP].pen == P_NONE) {
return;
}
mp = svgpt(gvc, p);
svg_printf(gvc, "<ellipse cx=\"%d\" cy=\"%d\"", mp.x, mp.y);
if (gvc->rot) {
int t = rx; t = rx; rx = ry; ry = t;
}
svg_printf(gvc, " rx=\"%d\" ry=\"%d\"", ROUND(rx), ROUND(ry));
svg_grstyle(gvc, &cstk[SP], filled);
svg_fputs(gvc, "/>\n");
}
static void svg_bezier(GVC_t * gvc, pointf * A, int n, int arrow_at_start,
int arrow_at_end)
{
if (cstk[SP].pen == P_NONE) {
return;
}
svg_fputs(gvc, "<path");
svg_grstyle(gvc, &cstk[SP], 0);
svg_fputs(gvc, " d=\"");
svgbzptarray(gvc, A, n);
svg_fputs(gvc, "\"/>\n");
}
static void svg_polygon(GVC_t * gvc, pointf * A, int n, int filled)
{
int i;
point p;
if (cstk[SP].pen == P_NONE) {
return;
}
svg_fputs(gvc, "<polygon");
svg_grstyle(gvc, &cstk[SP], filled);
svg_fputs(gvc, " points=\"");
for (i = 0; i < n; i++) {
p = svgpt(gvc, A[i]);
svg_printf(gvc, "%d,%d ", p.x, p.y);
}
p = svgpt(gvc, A[0]);
svg_printf(gvc, "%d,%d", p.x, p.y);
svg_fputs(gvc, "\"/>\n");
}
static void svg_polyline(GVC_t * gvc, pointf * A, int n)
{
int i;
point p;
if (cstk[SP].pen == P_NONE) {
return;
}
svg_fputs(gvc, "<polyline");
svg_grstyle(gvc, &cstk[SP], 0);
svg_fputs(gvc, " points=\"");
for (i = 0; i < n; i++) {
p = svgpt(gvc, A[i]);
svg_printf(gvc, "%d,%d ", p.x, p.y);
}
svg_fputs(gvc, "\"/>\n");
}
static void svg_user_shape(GVC_t * gvc, char *name, pointf * A, int np,
int filled)
{
int i;
point p;
point sz;
char *imagefile;
int minx, miny;
node_t *n = gvc->n;
pointf PF;
if (cstk[SP].pen == P_NONE) {
return;
}
imagefile = agget(n, "shapefile");
if (imagefile == 0) {
svg_polygon(gvc, A, np, filled);
return;
}
p = ND_coord_i(n);
p.x -= ND_lw_i(n);
p.y += ND_ht_i(n) / 2;
P2PF(p, PF);
p = svgpt(gvc, PF);
sz.x = ROUND((ND_lw_i(n) + ND_rw_i(n)));
sz.y = ROUND(ND_ht_i(n));
svg_fputs(gvc, "<clipPath id=\"mypath");
svg_fputs(gvc, name);
svg_fputs(gvc, n->name);
svg_fputs(gvc, "\">\n<polygon points=\"");
minx = svgpt(gvc, A[0]).x;
miny = svgpt(gvc, A[0]).y;
for (i = 0; i < np; i++) {
p = svgpt(gvc, A[i]);
if (p.x < minx)
minx = p.x;
if (p.y < miny)
miny = p.y;
svg_printf(gvc, "%d,%d ", p.x, p.y);
}
p = svgpt(gvc, A[0]);
svg_printf(gvc, "%d,%d ", p.x, p.y);
svg_fputs(gvc, "\"/>\n</clipPath>\n<image xlink:href=\"");
svg_fputs(gvc, imagefile);
svg_printf(gvc, "\" width=\"%dpx\" height=\"%dpx\"", sz.x, sz.y);
svg_fputs(gvc, " preserveAspectRatio=\"xMidYMid meet\"");
svg_printf(gvc, " x=\"%d\" y=\"%d\"", minx, miny);
svg_fputs(gvc, " clip-path=\"url(#mypath");
svg_fputs(gvc, name);
svg_fputs(gvc, n->name);
svg_fputs(gvc, ")\"/>\n");
}
gvrender_engine_t gvre_SVG = {
0,
0,
svg_begin_job,
0,
svg_begin_graph,
svg_end_graph,
svg_begin_page,
svg_end_page,
svg_begin_layer,
svg_end_layer,
svg_begin_cluster,
svg_end_cluster,
0,
0,
0,
0,
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,
0,
svg_user_shape,
0
};