/*
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
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef _UWIN
#ifndef DEFAULT_FONTPATH
#define DEFAULT_FONTPATH "/win/fonts"
#endif /* DEFAULT_FONTPATH */
#else
#ifndef MSWIN32
#ifndef DEFAULT_FONTPATH
#define DEFAULT_FONTPATH "/usr/share/ttf:/usr/local/share/ttf:/usr/share/fonts/ttf:/usr/local/share/fonts/ttf:/usr/lib/fonts:/usr/local/lib/fonts:/usr/lib/fonts/ttf:/usr/local/lib/fonts/ttf:/usr/common/graphviz/lib/fonts/ttf:/windows/fonts:/dos/windows/fonts:/usr/add-on/share/ttf:."
#endif /* DEFAULT_FONTPATH */
#else
#ifndef DEFAULT_FONTPATH
#define DEFAULT_FONTPATH "%WINDIR%/FONTS;C:/WINDOWS/FONTS;C:/WINNT/Fonts;C:/winnt/fonts"
#endif /* DEFAULT_FONTPATH */
#endif /* MSWIN32 */
#endif /* _UWIN */
#include "render.h"
#include "gd.h"
#include "utils.h"
#include <fcntl.h>
#ifdef MSWIN32
#include <io.h>
#endif
#define SCALE (GD_RESOLUTION/72.0)
#define BEZIERSUBDIVISION 10
/* fontsize at which text is omitted entirely */
#define FONTSIZE_MUCH_TOO_SMALL 0.15
/* fontsize at which text is rendered by a simple line */
#define FONTSIZE_TOO_SMALL 1.5
/* font modifiers */
#define REGULAR 0
#define BOLD 1
#define ITALIC 2
/* patterns */
#define P_SOLID 0
#define P_DOTTED 4
#define P_DASHED 11
#define P_NONE 15
/* bold line constant */
#define WIDTH_NORMAL 1
#define WIDTH_BOLD 3
static int N_pages;
static point Pages;
static double Scale;
static pointf Offset;
static double ArgScale;
static int Rot;
static box PB;
static gdImagePtr im;
static Dict_t *ImageDict;
typedef struct context_t {
int pencolor, fillcolor;
char *fontfam, fontopt, font_was_set, pen, fill, penwidth;
double fontsz;
} context_t;
#define MAXNEST 4
static context_t cstk[MAXNEST];
static int SP;
static node_t *Curnode;
static void gd_reset(void)
{
}
int black, white, transparent;
static void init_gd()
{
SP = 0;
/* must create default background color first... */
/* we have to force the background to be filled for some reason */
white = gdImageColorResolve(im, gdRedMax,gdGreenMax,gdBlueMax);
gdImageFilledRectangle(im, 0, 0, im->sx-1, im->sy-1, white);
black = gdImageColorResolve(im, 0, 0, 0);
/* transparent uses an rgb value very close to white
so that formats like GIF that don't support
transparency show a white background */
transparent = gdImageColorResolveAlpha(im, gdRedMax,gdGreenMax,gdBlueMax-1, gdAlphaTransparent);
gdImageColorTransparent(im, transparent);
cstk[0].pencolor = black; /* set pen black*/
cstk[0].fillcolor = black; /* set fill black*/
cstk[0].fontfam = "times"; /* font family name */
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;
}
pointf gdpt(pointf p)
{
pointf rv;
if (Rot == 0) {
rv.x = PB.LL.x + p.x * Scale + Offset.x;
rv.y = PB.UR.y - 1 - p.y * Scale - Offset.y;
} else {
rv.x = PB.UR.x - 1 - p.y * Scale - Offset.x;
rv.y = PB.UR.y - 1 - p.x * Scale - Offset.y;
}
return rv;
}
void gd_font(context_t* cp)
{
/*
char *fw, *fa;
fw = fa = "Regular";
switch (cp->fontopt) {
case BOLD:
fw = "Bold";
break;
case ITALIC:
fa = "Italic";
break;
}
*/
}
static void gd_begin_job(FILE *ofp, graph_t *g, char **lib, char *user,
char *info[], point pages)
{
Pages = pages;
N_pages = pages.x * pages.y;
#ifdef MYTRACE
fprintf(stderr,"gd_begin_job\n");
#endif
}
static void gd_end_job(void)
{
#ifdef MYTRACE
fprintf(stderr,"gd_end_job\n");
#endif
}
void gd_begin_graph(graph_t* g, box bb, point pb)
{
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;
}
static void gd_begin_graph_to_file(graph_t* g, box bb, point pb)
{
gd_begin_graph(g, bb, pb);
if (Verbose)
fprintf(stderr,"%s: allocating a %dK GD image\n",
CmdName, (PB.UR.x - PB.LL.x - 1) * (PB.UR.y - PB.LL.y - 1) / 1024);
if (mapbool(agget(g,"truecolor")))
im = gdImageCreateTrueColor((PB.UR.x - PB.LL.x - 1), (PB.UR.y - PB.LL.y - 1));
else
im = gdImageCreate((PB.UR.x - PB.LL.x - 1), (PB.UR.y - PB.LL.y - 1));
init_gd();
#ifdef MYTRACE
fprintf(stderr,"gd_begin_graph_to_file\n");
#endif
}
static void gd_begin_graph_to_memory(graph_t* g, box bb, point pb)
{
gd_begin_graph(g, bb, pb);
if (Verbose)
fprintf(stderr,"%s: using existing GD image\n", CmdName);
im = *(gdImagePtr *)Output_file;
init_gd();
#ifdef MYTRACE
fprintf(stderr,"gd_begin_graph_to_memory\n");
#endif
}
static void gd_end_graph_to_file(void)
{
/*
* Windows will do \n -> \r\n translations on stdout unless told otherwise.
*/
#ifdef HAVE_SETMODE
#ifdef O_BINARY
setmode(fileno(Output_file), O_BINARY);
#endif
#endif
/*
* Write IM to OUTFILE as a JFIF-formatted JPEG image, using quality
* JPEG_QUALITY. If JPEG_QUALITY is in the range 0-100, increasing values
* represent higher quality but also larger image size. If JPEG_QUALITY is
* negative, the IJG JPEG library's default quality is used (which
* should be near optimal for many applications). See the IJG JPEG
* library documentation for more details. */
#define JPEG_QUALITY -1
if (Output_lang == GD) {
gdImageGd(im, Output_file);
#ifdef HAVE_LIBZ
} else if (Output_lang == GD2) {
#define GD2_CHUNKSIZE 128
#define GD2_RAW 1
#define GD2_COMPRESSED 2
gdImageGd2(im, Output_file, GD2_CHUNKSIZE, GD2_COMPRESSED);
#endif
#ifdef WITH_GIF
} else if (Output_lang == GIF) {
gdImageGif(im, Output_file);
#endif
#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
} else if (Output_lang == PNG) {
gdImagePng(im, Output_file);
#endif
#endif
#ifdef HAVE_LIBJPEG
} else if (Output_lang == JPEG) {
gdImageJpeg(im, Output_file, JPEG_QUALITY);
#endif
} else if (Output_lang == WBMP) {
/* Use black for the foreground color for the B&W wbmp image. */
gdImageWBMP(im, black, Output_file);
#ifdef HAVE_LIBXPM
} else if (Output_lang == XBM) {
gdImageXbm(im, Output_file);
#endif
}
if (ImageDict) {
dtclose(ImageDict); ImageDict = 0;
}
gdImageDestroy(im);
#ifdef MYTRACE
fprintf(stderr,"gd_end_graph_to_file\n");
#endif
}
static void gd_end_graph_to_memory(void)
{
/* leave image in memory to be handled by Gdtclft output routines */
#ifdef MYTRACE
fprintf(stderr,"gd_end_graph_to_memory\n");
#endif
}
void
gd_begin_page(graph_t *g, point page, double scale, int rot, point offset)
{
int page_number;
point sz;
ArgScale = scale;
Scale = scale * SCALE;
Rot = rot;
page_number = page.x + page.y * Pages.x + 1;
sz = sub_points(PB.UR, PB.LL);
#ifdef MYTRACE
fprintf(stderr,"gd_begin_page\n");
fprintf(stderr," page=%d,%d offset=%d,%d\n",page.x,page.y,offset.x,offset.y);
fprintf(stderr," page_number=%d\n",page_number);
#endif
}
void gd_end_page(void) {
#ifdef MYTRACE
fprintf(stderr,"gd_end_page\n");
#endif
}
static void gd_begin_cluster(graph_t* g) { }
static void gd_end_cluster(void) { }
static void gd_begin_nodes(void) { }
static void gd_end_nodes(void) { }
static void gd_begin_edges(void) { }
static void gd_end_edges(void) { }
static void gd_begin_node(node_t* n) { Curnode = n; }
static void gd_end_node(void) { }
static void gd_begin_edge(edge_t* e) { }
static void gd_end_edge(void) { }
static void
gd_begin_context(void)
{
assert(SP + 1 < MAXNEST);
cstk[SP + 1] = cstk[SP];
SP++;
}
static void
gd_end_context(void)
{
int psp = SP - 1;
assert(SP > 0);
if (cstk[SP].font_was_set)
gd_font(&(cstk[psp]));
SP = psp;
}
static void
gd_set_font(char* name, double size)
{
char *p, *q;
context_t *cp;
cp = &(cstk[SP]);
cp->font_was_set = TRUE;
cp->fontsz = ArgScale * size;
p = strdup(name);
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;
gd_font(&cstk[SP]);
}
static int gd_resolve_color(char* name)
{
color_t color;
if (!(strcmp(name,"transparent"))) {
/* special case for "transparent" color */
return gdImageGetTransparent(im);
}
else {
colorxlate(name,&color,RGBA_BYTE);
return gdImageColorResolve(im,color.u.rgba[0],color.u.rgba[1],color.u.rgba[2]);
}
}
static void gd_set_pencolor(char* name)
{
cstk[SP].pencolor = gd_resolve_color(name);
}
static void gd_set_fillcolor(char* name)
{
cstk[SP].fillcolor = gd_resolve_color(name);
}
static void
gd_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 (AGWARN, "gd_set_style: unsupported style %s - ignoring\n",
line);
}
}
/* sometimes fonts are stored under a different name */
char *
gd_alternate_fontlist(char *font)
{
char *fontlist;
fontlist = font;
if (strcasecmp(font,"Times-Roman")==0)
fontlist = "Times-Roman Times_New_Roman Times-New-Roman TimesNewRoman Times times";
else if (strcasecmp(font,"Times-New-Roman")==0)
fontlist = "Times-New-Roman Times_New_Roman TimesNewRoman Times-Roman Times times";
else if (strcasecmp(font,"Times_New_Roman")==0)
fontlist = "Times_New_Roman Times-New-Roman TimesNewRoman Times-Roman Times times";
else if (strcasecmp(font,"TimesNewRoman")==0)
fontlist = "TimesNewRoman Times_New_Roman Times-New-Roman Times-Roman Times times";
else if (strcasecmp(font,"Times")==0)
fontlist = "Times times Times-Roman Times_New_Roman Times-New-Roman TimesNewRoman";
else if (strcasecmp(font,"Helvetica")==0)
fontlist = "Helvetica arial";
else if (strcasecmp(font,"Arial")==0)
fontlist = "Arial arial";
else if (strcasecmp(font,"arialb")==0)
fontlist = "arialb Arial-Bold";
else if (strcasecmp(font, "ariali")==0)
fontlist = "ariali Arial-Italic";
else if (strcasecmp(font,"Courier")==0)
fontlist = "Courier cour Courier-New Courier_New";
else if (strcasecmp(font,"Courier-New")==0)
fontlist = "Courier-New Courier_New Courier cour";
else if (strcasecmp(font,"Courier_New")==0)
fontlist = "Courier_New Courier-New Courier cour";
return fontlist;
}
void gd_missingfont(char *err, char *fontreq)
{
static char *lastmissing = 0;
static int n_errors = 0;
char *p;
if (n_errors >= 20) return;
if ((lastmissing == 0) || (strcmp(lastmissing,fontreq))) {
if (!(p=getenv("GDFONTPATH"))) p = DEFAULT_FONTPATH;
agerr(AGERR, "%s : %s in %s\n",err,fontreq,p);
if (lastmissing) free(lastmissing);
lastmissing = strdup(fontreq);
n_errors++;
if (n_errors >= 20) agerr(AGWARN, "(font errors suppressed)\n");
}
}
extern gdFontPtr gdFontTiny, gdFontSmall, gdFontMediumBold, gdFontLarge, gdFontGiant;
static void
gd_textline(point p, textline_t *line)
{
char *fontlist, *err;
pointf mp,ep;
int brect[8];
char *str = line->str;
double fontsz = cstk[SP].fontsz;
if (cstk[SP].pen == P_NONE) return;
fontlist = gd_alternate_fontlist(cstk[SP].fontfam);
switch(line->just) {
case 'l':
mp.x = p.x;
break;
case 'r':
mp.x = p.x - line->width;
break;
default:
case 'n':
mp.x = p.x - line->width / 2;
break;
}
ep.y = mp.y = p.y;
ep.x = mp.x + line->width;
mp = gdpt(mp);
if (fontsz <= FONTSIZE_MUCH_TOO_SMALL) {
/* ignore entirely */
} else if (fontsz <= FONTSIZE_TOO_SMALL) {
/* draw line in place of text */
ep = gdpt(ep);
gdImageLine(im, ROUND(mp.x), ROUND(mp.y),
ROUND(ep.x), ROUND(ep.y),
cstk[SP].pencolor);
} else {
err = gdImageStringFT(im, brect, cstk[SP].pencolor,
fontlist, fontsz, (Rot? 90.0 : 0.0) * PI / 180.0,
ROUND(mp.x), ROUND(mp.y), str);
if (err) {
/* revert to builtin fonts */
gd_missingfont (err, cstk[SP].fontfam);
if (fontsz <= 8.5) {
gdImageString(im, gdFontTiny,
ROUND(mp.x), ROUND(mp.y-9.),
(unsigned char *)str, cstk[SP].pencolor);
} else if (fontsz <= 9.5) {
gdImageString(im, gdFontSmall,
ROUND(mp.x), ROUND(mp.y-12.),
(unsigned char *)str, cstk[SP].pencolor);
} else if (fontsz <= 10.5) {
gdImageString(im, gdFontMediumBold,
ROUND(mp.x), ROUND(mp.y-13.),
(unsigned char *)str, cstk[SP].pencolor);
} else if (fontsz <= 11.5) {
gdImageString(im, gdFontLarge,
ROUND(mp.x), ROUND(mp.y-14.),
(unsigned char *)str, cstk[SP].pencolor);
} else {
gdImageString(im, gdFontGiant,
ROUND(mp.x), ROUND(mp.y-15.),
(unsigned char *)str, cstk[SP].pencolor);
}
}
}
}
point gd_textsize(char *str, char *fontname, double fontsz)
{
char *fontlist,*err;
point rv;
int brect[8];
fontlist = gd_alternate_fontlist(fontname);
rv.x = rv.y = 0.0;
if (fontlist && *str) {
if (fontsz <= FONTSIZE_MUCH_TOO_SMALL) {
/* ignore entirely */
rv.x = rv.y = 0;
return rv;
} else if (fontsz <= FONTSIZE_TOO_SMALL) {
/* draw line in place of text */
/* fake a finite fontsize so that line length is calculated */
fontsz = FONTSIZE_TOO_SMALL;
}
/* call gdImageStringFT with null *im to get brect */
err = gdImageStringFT(NULL, brect, -1, fontlist,
fontsz, 0, 0, 0, str);
if (!err) {
rv.x = (brect[4] - brect[0]);
/* rv.y = (brect[5] - brect[1]); */
rv.y = (brect[5] - 0 ); /* ignore descenders */
rv.x /= SCALE; rv.y /= SCALE;
}
}
return rv;
}
static void
gd_bezier(point* A, int n, int arrow_at_start, int arrow_at_end)
{
pointf p0, p1, V[4];
int i, j, step;
int style[20];
int pen, width;
gdImagePtr brush = NULL;
if (cstk[SP].pen != P_NONE) {
if (cstk[SP].pen == P_DASHED) {
for (i = 0; i < 10; i++)
style[i] = cstk[SP].pencolor;
for (; i < 20; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 20);
pen = gdStyled;
} else if (cstk[SP].pen == P_DOTTED) {
for (i = 0; i < 2; i++)
style[i] = cstk[SP].pencolor;
for (; i < 12; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 12);
pen = gdStyled;
} else {
pen = cstk[SP].pencolor;
}
#if 0
if (cstk[SP].penwidth != WIDTH_NORMAL) {
width=cstk[SP].penwidth;
brush = gdImageCreate(width,width);
gdImagePaletteCopy(brush, im);
gdImageFilledRectangle(brush,
0,0,width-1, width-1, cstk[SP].pencolor);
gdImageSetBrush(im, brush);
if (pen == gdStyled) pen = gdStyledBrushed;
else pen = gdBrushed;
}
#else
width = cstk[SP].penwidth;
gdImageSetThickness(im, width);
#endif
V[3].x = A[0].x; V[3].y = A[0].y;
for (i = 0; i+3 < n; i += 3) {
V[0] = V[3];
for (j = 1; j <= 3; j++) {
V[j].x = A[i+j].x; V[j].y = A[i+j].y;
}
p0 = gdpt(V[0]);
for (step = 1; step <= BEZIERSUBDIVISION; step++) {
p1 = gdpt(Bezier(V, 3, (double)step/BEZIERSUBDIVISION, NULL, NULL));
gdImageLine(im, ROUND(p0.x), ROUND(p0.y),
ROUND(p1.x), ROUND(p1.y), pen);
p0 = p1;
}
}
if (brush)
gdImageDestroy(brush);
}
}
static void
gd_polygon(point *A, int n, int filled)
{
pointf p;
int i;
gdPoint *points;
int style[20];
int pen, width;
gdImagePtr brush = NULL;
if (cstk[SP].pen != P_NONE) {
if (cstk[SP].pen == P_DASHED) {
for (i = 0; i < 10; i++)
style[i] = cstk[SP].pencolor;
for (; i < 20; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 20);
pen = gdStyled;
} else if (cstk[SP].pen == P_DOTTED) {
for (i = 0; i < 2; i++)
style[i] = cstk[SP].pencolor;
for (; i < 12; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 12);
pen = gdStyled;
} else {
pen = cstk[SP].pencolor;
}
#if 1
/* use brush instead of Thickness to improve end butts */
gdImageSetThickness(im, WIDTH_NORMAL);
if (cstk[SP].penwidth != WIDTH_NORMAL) {
width=cstk[SP].penwidth * Scale;
brush = gdImageCreate(width,width);
gdImagePaletteCopy(brush, im);
gdImageFilledRectangle(brush,
0,0,width-1, width-1, cstk[SP].pencolor);
gdImageSetBrush(im, brush);
if (pen == gdStyled) pen = gdStyledBrushed;
else pen = gdBrushed;
}
#else
width = cstk[SP].penwidth;
gdImageSetThickness(im, width);
#endif
points = N_GNEW(n,gdPoint);
for (i = 0; i < n; i++) {
p.x = A[i].x; p.y = A[i].y;
p = gdpt(p);
points[i].x = ROUND(p.x); points[i].y = ROUND(p.y);
}
if (filled) gdImageFilledPolygon(im, points, n, cstk[SP].fillcolor);
gdImagePolygon(im, points, n, pen);
free(points);
if (brush)
gdImageDestroy(brush);
}
}
static void
gd_ellipse(point p, int rx, int ry, int filled)
{
pointf mp;
int i;
int style[40]; /* need 2* size for arcs, I don't know why */
int pen, width;
gdImagePtr brush = NULL;
if (cstk[SP].pen != P_NONE) {
if (cstk[SP].pen == P_DASHED) {
for (i = 0; i < 20; i++)
style[i] = cstk[SP].pencolor;
for (; i < 40; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 40);
pen = gdStyled;
} else if (cstk[SP].pen == P_DOTTED) {
for (i = 0; i < 2; i++)
style[i] = cstk[SP].pencolor;
for (; i < 24; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 24);
pen = gdStyled;
} else {
pen = cstk[SP].pencolor;
}
#if 1
/* use brush instead of Thickness to improve outline appearance */
gdImageSetThickness(im, WIDTH_NORMAL);
if (cstk[SP].penwidth != WIDTH_NORMAL) {
width = cstk[SP].penwidth;
brush = gdImageCreate(width,width);
gdImagePaletteCopy(brush, im);
gdImageFilledRectangle(brush,
0,0,width-1, width-1, cstk[SP].pencolor);
gdImageSetBrush(im, brush);
if (pen == gdStyled) pen = gdStyledBrushed;
else pen = gdBrushed;
}
#else
width = cstk[SP].penwidth;
gdImageSetThickness(im, width);
#endif
if (Rot) {int t; t = rx; rx = ry; ry = t;}
mp.x = p.x; mp.y = p.y;
mp = gdpt(mp);
if (filled) {
gdImageFilledEllipse(im, ROUND(mp.x), ROUND(mp.y),
ROUND(Scale*(rx + rx)), ROUND(Scale*(ry + ry)),
cstk[SP].fillcolor);
}
gdImageArc(im, ROUND(mp.x), ROUND(mp.y),
ROUND(Scale*(rx + rx)), ROUND(Scale*(ry + ry)), 0, 360, pen);
if (brush)
gdImageDestroy(brush);
}
}
static void
gd_polyline(point* A, int n)
{
pointf p, p1;
int i;
int style[20];
int pen, width;
gdImagePtr brush = NULL;
if (cstk[SP].pen != P_NONE) {
if (cstk[SP].pen == P_DASHED) {
for (i = 0; i < 10; i++)
style[i] = cstk[SP].pencolor;
for (; i < 20; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 20);
pen = gdStyled;
} else if (cstk[SP].pen == P_DOTTED) {
for (i = 0; i < 2; i++)
style[i] = cstk[SP].pencolor;
for (; i < 12; i++)
style[i] = gdTransparent;
gdImageSetStyle(im, style, 12);
pen = gdStyled;
} else {
pen = cstk[SP].pencolor;
}
#if 0
if (cstk[SP].penwidth != WIDTH_NORMAL) {
width = cstk[SP].penwidth;
brush = gdImageCreate(width,width);
gdImagePaletteCopy(brush, im);
gdImageFilledRectangle(brush,
0,0,width-1,width-1,cstk[SP].pencolor);
gdImageSetBrush(im, brush);
if (pen == gdStyled) pen = gdStyledBrushed;
else pen = gdBrushed;
}
#else
width = cstk[SP].penwidth;
gdImageSetThickness(im, width);
#endif
p.x = A[0].x;
p.y = A[0].y;
p = gdpt(p);
for (i = 1; i < n; i++) {
p1.x = A[i].x;
p1.y = A[i].y;
p1 = gdpt(p1);
gdImageLine(im, ROUND(p.x), ROUND(p.y),
ROUND(p1.x), ROUND(p1.y), pen);
p.x = p1.x;
p.y = p1.y;
}
if (brush)
gdImageDestroy(brush);
}
}
static gdImagePtr loadimage(char *name)
{
gdImagePtr rv = 0;
char *imagefile,*suffix;
FILE *in = NULL;
char *cmd = 0;
#ifdef HAVE_CURL
char *curlopts;
#endif
imagefile = name;
if (!imagefile) return 0;
if (!strncmp(imagefile,"file:",5) && !httpcheck(imagefile)) {
imagefile = &imagefile[5];
if (!strncmp(imagefile,"//",2)) imagefile = &imagefile[2];
#ifndef MSWIN32
in = fopen (imagefile, "r");
#else
in = fopen (imagefile, "rb");
#endif
cmd = N_GNEW(16,char);
strcpy(cmd,"fopen()");
}
#ifdef HAVE_CURL
else {
cmd = N_GNEW(strlen(imagefile) + strlen(CURLCMD) + 16,char);
if (httpcheck || !(curlopts = agget(Curnode,"curlopts")))
curlopts = CURLOPTS;
sprintf(cmd,"%s %s \"%s\"",CURLCMD,curlopts,imagefile);
in = popen(cmd,"r");
}
#endif
if (!in)
agerr(AGERR, "couldn't open image file %s via %s\n",name,cmd);
else {
suffix = strrchr(imagefile,'.');
if (!suffix) suffix = imagefile; else suffix++;
if (!strcasecmp(suffix,"wbmp")) rv = gdImageCreateFromWBMP(in);
#ifdef WITH_GIF
else if (!strcasecmp(suffix,"gif")) rv = gdImageCreateFromGif(in);
#endif
#ifdef HAVE_LIBPNG
else if (!strcasecmp(suffix,"png")) rv = gdImageCreateFromPng(in);
#endif
#ifdef HAVE_LIBJPEG
else if (!strcasecmp(suffix,"jpeg")||!strcasecmp(suffix,"jpg")) rv = gdImageCreateFromJpeg(in);
#endif
else if (!strcasecmp(suffix,"xbm")) rv = gdImageCreateFromXbm(in);
else agerr(AGERR, "image file %s suffix not recognized\n",name);
fclose(in);
if (!rv) agerr(AGERR, "image file %s contents were not recognized\n",name);
}
if (cmd) free(cmd);
return rv;
}
typedef struct imagerec_s {
Dtlink_t link;
char *name;
gdImagePtr im;
} imagerec_t;
static void imagerec_free(Dict_t *dict, Void_t *p, Dtdisc_t *disc)
{
gdImagePtr im = ((imagerec_t*)p)->im;
if (im) gdImageDestroy(im);
}
static Dtdisc_t ImageDictDisc = {
offsetof(imagerec_t,name), /* key */
-1, /* size */
0, /* link offset */
NIL(Dtmake_f),
imagerec_free,
NIL(Dtcompar_f),
NIL(Dthash_f),
NIL(Dtmemory_f),
NIL(Dtevent_f)
};
static gdImagePtr getimage(char *name)
{
imagerec_t probe, *val;
if (!name) return 0; /* cdt does not like NULL keys */
if (!ImageDict) ImageDict = dtopen(&ImageDictDisc,Dttree);
probe.name = name;
val = dtsearch(ImageDict,&probe);
if (!val) {
val = GNEW(imagerec_t);
val->name = name;
val->im = loadimage(name);
dtinsert(ImageDict,val);
}
return val->im;
}
static void
gd_user_shape(char *name, point *A, int n, int filled)
{
gdImagePtr im2 = 0;
pointf destul, destlr;
pointf ul, lr; /* upper left, lower right */
int i;
im2 = getimage(agget(Curnode,"shapefile"));
if (im2) {
/* compute dest origin and size */
ul.x = lr.x = A[0].x; ul.y = lr.y = A[0].y;
for (i = 1; i < n; i++) {
if (ul.x > A[i].x) ul.x = A[i].x;
if (ul.y < A[i].y) ul.y = A[i].y;
if (lr.y > A[i].y) lr.y = A[i].y;
if (lr.x < A[i].x) lr.x = A[i].x;
}
destul = gdpt(ul);
destlr = gdpt(lr);
gdImageCopyResized(im,im2,ROUND(destul.x),ROUND(destul.y),0,0,ROUND(destlr.x - destul.x),ROUND(destlr.y - destul.y),im2->sx,im2->sy);
}
}
point gd_user_shape_size(node_t *n, char *imagefile)
{
point rv;
gdImagePtr im;
Curnode = n;
im = getimage(imagefile);
if (im) {rv.x = im->sx / SCALE; rv.y = im->sy / SCALE; }
else rv.x = rv.y = 0;
return rv;
}
codegen_t GD_CodeGen = {
gd_reset,
gd_begin_job, gd_end_job,
gd_begin_graph_to_file, gd_end_graph_to_file,
gd_begin_page, gd_end_page,
gd_begin_cluster, gd_end_cluster,
gd_begin_nodes, gd_end_nodes,
gd_begin_edges, gd_end_edges,
gd_begin_node, gd_end_node,
gd_begin_edge, gd_end_edge,
gd_begin_context, gd_end_context,
gd_set_font, gd_textline,
gd_set_pencolor, gd_set_fillcolor, gd_set_style,
gd_ellipse, gd_polygon,
gd_bezier, gd_polyline,
0 /* gd_arrowhead */, gd_user_shape,
0 /* gd_comment */, gd_textsize,
gd_user_shape_size
};
codegen_t memGD_CodeGen = { /* see tcldot */
gd_reset,
gd_begin_job, gd_end_job,
gd_begin_graph_to_memory, gd_end_graph_to_memory,
gd_begin_page, gd_end_page,
gd_begin_cluster, gd_end_cluster,
gd_begin_nodes, gd_end_nodes,
gd_begin_edges, gd_end_edges,
gd_begin_node, gd_end_node,
gd_begin_edge, gd_end_edge,
gd_begin_context, gd_end_context,
gd_set_font, gd_textline,
gd_set_pencolor, gd_set_fillcolor, gd_set_style,
gd_ellipse, gd_polygon,
gd_bezier, gd_polyline,
0 /* gd_arrowhead */, gd_user_shape,
0 /* gd_comment */, gd_textsize,
gd_user_shape_size
};
|