/*
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
#include "render.h"
#include "xbuf.h"
int Output_lang_count;
char *Output_langs[MAX_PRODUCTS];
int Output_lang;
int Output_file_count;
char *Output_files[MAX_PRODUCTS];
FILE *Output_file;
FILE *Output_handles[MAX_PRODUCTS];
int files_opened_once=0;
codegen_t *CodeGen;
int y_invert; /* invert y in bounding box */
static int y_off; /* ymin + ymax */
static double yf_off; /* y_off in inches */
static int e_arrows; /* graph has edges with end arrows */
static int s_arrows; /* graph has edges with start arrows */
static void extend_attrs (graph_t* g);
/* macros for inverting the y coordinate with the bounding box */
#define Y(y) (y_invert ? (y_off - (y)) : (y))
#define YF(y) (y_invert ? (yf_off - (y)) : (y))
void dotneato_set_margins(graph_t* g)
{
double xf, yf;
char *p;
int i;
/* margins */
if ((p = agget(g,"margin"))) {
i = sscanf(p,"%lf,%lf",&xf,&yf);
if (i > 0) GD_drawing(g)->margin.x = GD_drawing(g)->margin.y = POINTS(xf);
if (i > 1) GD_drawing(g)->margin.y = POINTS(yf);
}
else {
/* set default margins depending on format */
switch (Output_lang) {
case GIF:
case PNG:
case JPEG:
case WBMP:
case GD:
case memGD:
case GD2:
case ISMAP:
case IMAP:
case CMAP:
case VRML:
case DIA:
case SVG:
case SVGZ:
GD_drawing(g)->margin.x = GD_drawing(g)->margin.y = DEFAULT_EMBED_MARGIN;
break;
case POSTSCRIPT:
case PDF:
case HPGL:
case PCL:
case MIF:
case METAPOST:
case FIG:
case VTX:
case ATTRIBUTED_DOT:
case PLAIN:
case PLAIN_EXT:
GD_drawing(g)->margin.x = GD_drawing(g)->margin.y = DEFAULT_MARGIN;
break;
case CANONICAL_DOT:
break;
}
}
}
/* chkOrder:
* Determine order of output.
* Output usually in breadth first graph walk order
*/
static int
chkOrder (graph_t* g)
{
char *p = agget(g,"outputorder");
if (p) {
char c = *p;
if ((c == 'n') && !strcmp(p+1,"odesfirst")) return EMIT_SORTED;
if ((c == 'e') && !strcmp(p+1,"edgesfirst")) return EMIT_EDGE_SORTED;
}
return 0;
}
void dotneato_write_one(graph_t* g)
{
dotneato_set_margins(g);
switch (Output_lang) {
case POSTSCRIPT:
case PDF:
case HPGL:
case PCL:
case MIF:
case PIC_format:
case GIF:
case PNG:
case JPEG:
case WBMP:
case GD:
case memGD:
case GD2:
case VRML:
case METAPOST:
case SVG:
case SVGZ:
emit_graph(g,chkOrder (g)); break;
case ISMAP:
case IMAP:
case CMAP:
/* output in breadth first graph walk order, but
* with nodes edges and nested clusters before
* clusters */
emit_graph(g,EMIT_CLUSTERS_LAST); break;
case FIG:
/* output color definition objects first */
emit_graph(g,EMIT_COLORS); break;
case VTX:
/* output sorted, i.e. all nodes then all edges */
emit_graph(g,EMIT_SORTED); break;
case DIA:
/* output in preorder traversal of the graph*/
emit_graph(g,EMIT_PREORDER);
break;
case EXTENDED_DOT:
attach_attrs(g);
extend_attrs(g);
agwrite(g,Output_file); break;
case ATTRIBUTED_DOT:
attach_attrs(g);
agwrite(g,Output_file); break;
case CANONICAL_DOT:
agwrite(g,Output_file); break;
case PLAIN:
attach_attrs(g); write_plain(g,Output_file); break;
case PLAIN_EXT:
attach_attrs(g); write_plain_ext(g,Output_file); break;
}
fflush(Output_file);
}
void
dotneato_write(graph_t* g)
{
int j;
for (j=0; j<Output_lang_count; j++) {
if (! files_opened_once) {
if (Output_files[j] == NULL) {
Output_handles[j] = stdout;
}
else {
Output_handles[j] = file_select(Output_files[j]);
}
Output_lang = lang_select(Output_langs[j], 1);
}
else {
Output_lang = lang_select(Output_langs[j], 0);
}
Output_file = Output_handles[j];
dotneato_write_one(g);
}
files_opened_once++;
}
void
dotneato_eof(void)
{
int j;
for (j=0; j<Output_lang_count; j++) {
if (files_opened_once) {
Output_file = Output_handles[j];
Output_lang = lang_select(Output_langs[j], 0);
emit_eof();
fclose(Output_file);
}
}
}
static void
set_record_rects (node_t* n, field_t* f, xbuf* xb)
{
int i;
char buf[BUFSIZ];
if (f->n_flds == 0) {
sprintf(buf, "%d,%d,%d,%d ",
f->b.LL.x + ND_coord_i(n).x,
Y(f->b.LL.y + ND_coord_i(n).y),
f->b.UR.x + ND_coord_i(n).x,
Y(f->b.UR.y + ND_coord_i(n).y));
xbput (xb, buf);
}
for (i = 0; i < f->n_flds; i++)
set_record_rects (n, f->fld[i], xb);
}
static attrsym_t *safe_dcl(graph_t *g, void *obj, char *name, char *def,
attrsym_t*(*fun)(Agraph_t*, char*, char*))
{
attrsym_t *a = agfindattr(obj,name);
if (a == NULL) a = fun(g,name,def);
return a;
}
void attach_attrs(graph_t* g)
{
int i,j,sides;
char buf[BUFSIZ]; /* Used only for small strings */
unsigned char xbuffer[BUFSIZ]; /* Initial buffer for xb */
xbuf xb;
node_t *n;
edge_t *e;
point pt;
e_arrows = s_arrows = 0;
if (y_invert) {
y_off = GD_bb(g).UR.y + GD_bb(g).LL.y;
yf_off = PS2INCH(y_off);
}
xbinit (&xb, BUFSIZ, xbuffer);
safe_dcl(g,g->proto->n,"pos","",agnodeattr);
safe_dcl(g,g->proto->n,"rects","",agnodeattr);
N_width = safe_dcl(g,g->proto->n,"width","",agnodeattr);
N_height = safe_dcl(g,g->proto->n,"height","",agnodeattr);
safe_dcl(g,g->proto->e,"pos","",agedgeattr);
if (GD_has_edge_labels(g) & EDGE_LABEL)
safe_dcl(g,g->proto->e,"lp","",agedgeattr);
if (GD_has_edge_labels(g) & HEAD_LABEL)
safe_dcl(g,g->proto->e,"head_lp","",agedgeattr);
if (GD_has_edge_labels(g) & TAIL_LABEL)
safe_dcl(g,g->proto->e,"tail_lp","",agedgeattr);
if (GD_label(g)) {
safe_dcl(g,g,"lp","",agraphattr);
if (GD_label(g)->text[0]) {
pt = GD_label(g)->p;
sprintf(buf,"%d,%d",pt.x,Y(pt.y));
agset(g,"lp",buf);
}
}
safe_dcl(g,g,"bb","",agraphattr);
for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
sprintf(buf,"%d,%d",ND_coord_i(n).x,Y(ND_coord_i(n).y));
agset(n,"pos",buf);
sprintf(buf,"%.2f",PS2INCH(ND_ht_i(n)));
agxset(n,N_height->index,buf);
sprintf(buf,"%.2f",PS2INCH(ND_lw_i(n) + ND_rw_i(n)));
agxset(n,N_width->index,buf);
if (strcmp (ND_shape(n)->name, "record") == 0) {
set_record_rects (n, ND_shape_info(n), &xb);
xbpop (&xb); /* get rid of last space */
agset(n,"rects",xbuse(&xb));
}
else {
extern void poly_init(node_t *);
polygon_t *poly;
int i;
if (N_vertices && (ND_shape(n)->initfn == poly_init)) {
poly = (polygon_t*) ND_shape_info(n);
sides = poly->sides;
if (sides < 3) {
char *p = agget(n,"samplepoints");
if (p) sides = atoi(p);
else sides = 8;
if (sides < 3) sides = 8;
}
for (i = 0; i < sides; i++) {
if (i > 0) xbputc (&xb, ' ');
if (poly->sides >= 3)
sprintf(buf,"%.3f %.3f",
poly->vertices[i].x,YF(poly->vertices[i].y));
else
sprintf(buf,"%.3f %.3f",
ND_width(n)/2.0 * cos(i/(double)sides * PI * 2.0),
YF(ND_height(n)/2.0 * sin(i/(double)sides* PI * 2.0)));
xbput(&xb, buf);
}
agxset(n,N_vertices->index,xbuse(&xb));
}
}
if (State >= GVSPLINES) {
for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
if (ED_edge_type(e) == IGNORED) continue;
for (i = 0; i < ED_spl(e)->size; i++) {
if (i > 0) xbputc (&xb, ';');
if (ED_spl(e)->list[i].sflag) {
s_arrows = 1;
sprintf (buf, "s,%d,%d ",
ED_spl(e)->list[i].sp.x,Y(ED_spl(e)->list[i].sp.y));
xbput(&xb, buf);
}
if (ED_spl(e)->list[i].eflag) {
e_arrows = 1;
sprintf (buf, "e,%d,%d ",
ED_spl(e)->list[i].ep.x,Y(ED_spl(e)->list[i].ep.y));
xbput(&xb, buf);
}
for (j = 0; j < ED_spl(e)->list[i].size; j++) {
if (j > 0) xbputc (&xb, ' ');
pt = ED_spl(e)->list[i].list[j];
sprintf(buf,"%d,%d",pt.x,Y(pt.y));
xbput(&xb, buf);
}
}
agset(e,"pos",xbuse(&xb));
if (ED_label(e)) {
pt = ED_label(e)->p;
sprintf(buf,"%d,%d",pt.x,Y(pt.y));
agset(e,"lp",buf);
}
if (ED_head_label(e)) {
pt = ED_head_label(e)->p;
sprintf(buf,"%d,%d",pt.x,Y(pt.y));
agset(e,"head_lp",buf);
}
if (ED_tail_label(e)) {
pt = ED_tail_label(e)->p;
sprintf(buf,"%d,%d",pt.x,Y(pt.y));
agset(e,"tail_lp",buf);
}
}
}
}
rec_attach_bb(g);
xbfree (&xb);
}
void rec_attach_bb(graph_t* g)
{
int c;
char buf[32];
point pt;
sprintf(buf,"%d,%d,%d,%d", GD_bb(g).LL.x, Y(GD_bb(g).LL.y),
GD_bb(g).UR.x, Y(GD_bb(g).UR.y));
agset(g,"bb",buf);
if (GD_label(g) && GD_label(g)->text[0]) {
pt = GD_label(g)->p;
sprintf(buf,"%d,%d",pt.x,Y(pt.y));
agset(g,"lp",buf);
}
for (c = 1; c <= GD_n_cluster(g); c++) rec_attach_bb(GD_clust(g)[c]);
}
static char *getoutputbuffer(char *str)
{
static char *rv;
static int len;
int req;
req = MAX(2 * strlen(str) + 2, BUFSIZ);
if (req > len) {
rv = ALLOC(req,rv,char);
len = req;
}
return rv;
}
static char *canonical(char *str)
{
return agstrcanon(str,getoutputbuffer(str));
}
static void writenodeandport(FILE *fp, char *node, char *port)
{
fprintf(fp,"%s",canonical(node)); /* slimey i know*/
if (port && *port) fprintf(fp,"%c%s",port[0],canonical(port+1));
}
/* FIXME - there must be a proper way to get port info - these are
* supposed to be private to libgraph - from libgraph.h */
#define TAILX 1
#define HEADX 2
/* _write_plain:
* Assumes y_invert parameters have been set.
* At present, this is done by a previous call to attach_attrs.
* As this appears to be the only reason to call attach_attrs for plain
* output, it may be cleaner to remove that call and handle y_invert
* here.
*/
void _write_plain(graph_t* g, FILE* f, boolean extend)
{
int i,j,splinePoints;
char *tport, *hport;
node_t *n;
edge_t *e;
bezier bz;
point pt;
setup_graph(g);
pt = GD_bb(g).UR;
fprintf(f,"graph %.3f %.3f %.3f\n",
GD_drawing(g)->scale, PS2INCH(pt.x), PS2INCH(pt.y));
for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
fprintf(f,"node %s ",canonical(n->name)); printptf(f,ND_coord_i(n));
fprintf(f," %.3f %.3f %s %s %s %s %s\n",
ND_width(n),ND_height(n),canonical(ND_label(n)->text),
late_nnstring(n,N_style,"solid"),
ND_shape(n)->name,
late_nnstring(n,N_color,DEFAULT_COLOR),
late_nnstring(n,N_fillcolor,DEFAULT_FILL));
}
for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
if (extend && e->attr) {
tport = e->attr[TAILX];
hport = e->attr[HEADX];
}
else tport = hport = "";
if (ED_spl(e)) {
splinePoints = 0;
for (i = 0; i < ED_spl(e)->size; i++) {
bz = ED_spl(e)->list[i];
splinePoints += bz.size;
}
fprintf(f,"edge ");
writenodeandport(f,e->tail->name,tport);
fprintf(f," ");
writenodeandport(f,e->head->name,hport);
fprintf(f," %d",splinePoints);
for (i = 0; i < ED_spl(e)->size; i++) {
bz = ED_spl(e)->list[i];
for (j = 0; j < bz.size; j++) printptf(f,bz.list[j]);
}
}
if (ED_label(e)) {
fprintf(f," %s",canonical(ED_label(e)->text));
printptf(f,ED_label(e)->p);
}
fprintf(f," %s %s\n",late_nnstring(e,E_style,"solid"),
late_nnstring(e,E_color,DEFAULT_COLOR));
}
}
fprintf(f,"stop\n");
}
void write_plain(graph_t* g, FILE* f)
{
_write_plain (g, f, FALSE);
}
void write_plain_ext(graph_t* g, FILE* f)
{
_write_plain (g, f, TRUE);
}
void printptf(FILE* f, point pt)
{
fprintf(f," %.3f %.3f",PS2INCH(pt.x),PS2INCH(Y(pt.y)));
}
int codegen_bezier_has_arrows(void)
{
return CodeGen &&
CodeGen->bezier_has_arrows
/* (CodeGen->arrowhead == 0)) */;
}
typedef void (*BJ)(FILE *,graph_t *, char **, char *, char **, point);
typedef void (*BG)(graph_t *, box bb, point pb);
typedef void (*BP)(graph_t *, point, double, int, point);
typedef void (*BC)(graph_t *);
typedef void (*BN)(node_t *);
typedef void (*BE)(edge_t *);
typedef void (*SA)(char *);
typedef void (*SS)(char **);
typedef void (*SF)(char *, double);
static xbuf outbuf;
static xbuf charbuf;
static void
xd_textline(point p, textline_t *line)
{
char buf[BUFSIZ];
int j;
xbputc(&charbuf, 'T');
switch(line->just) {
case 'l':
j = -1;
break;
case 'r':
j = 1;
break;
default:
case 'n':
j = 0;
break;
}
sprintf(buf, " %d %d %d %d %d -", p.x, Y(p.y), j,
line->width, strlen(line->str));
xbput(&charbuf, buf);
xbput(&charbuf, line->str);
xbputc(&charbuf, ' ');
}
static void
xd_ellipse(point p, int rx, int ry, int filled)
{
char buf[BUFSIZ];
xbputc(&outbuf, (filled ? 'E' : 'e'));
sprintf(buf, " %d %d %d %d ", p.x, Y(p.y), rx, ry);
xbput(&outbuf, buf);
}
static void
points (char c,point *A,int n)
{
char buf[BUFSIZ];
int i;
point p;
xbputc(&outbuf, c);
sprintf(buf, " %d ", n);
xbput(&outbuf, buf);
for (i = 0; i < n; i++) {
p = A[i];
sprintf(buf, "%d %d ", p.x, Y(p.y));
xbput(&outbuf, buf);
}
}
static void
xd_polygon(point *A, int n, int filled)
{
points ((filled ? 'P' : 'p'),A,n);
}
static void
xd_bezier(point *A, int n, int arrow_at_start, int arrow_at_end)
{
points ('B',A,n);
}
static void
xd_polyline(point *A,int n)
{
points ('L',A,n);
}
static Agraph_t* cluster_g;
static attrsym_t* g_draw;
static attrsym_t* g_l_draw;
static void
xd_begin_cluster(Agraph_t* g)
{
cluster_g = g;
}
static void
xd_end_cluster()
{
agxset (cluster_g, g_draw->index, xbuse(&outbuf));
agxset (cluster_g, g_l_draw->index, xbuse(&charbuf));
}
static void dummy() {}
codegen_t XDot_CodeGen = {
dummy,
(BJ)dummy, dummy,
(BG)dummy, dummy,
(BP)dummy, dummy,
xd_begin_cluster, xd_end_cluster,
dummy, dummy,
dummy, dummy,
(BN)dummy, dummy,
(BE)dummy, dummy,
dummy, dummy,
(SF)dummy, xd_textline,
(SA)dummy, (SA)dummy, (SS)dummy,
xd_ellipse, xd_polygon,
xd_bezier, xd_polyline,
0 /* xd_arrowhead */, 0 /* xd_user_shape*/ ,
0 /* xd_comment */, 0 /* xd_textsize */
};
static int
isInvis (char* style)
{
char** styles = 0;
char** sp;
char* p;
if (style[0]) {
styles = parse_style(style);
sp = styles;
while ((p = *sp++)) {
if (streq(p, "invis")) return 1;
}
}
return 0;
}
/*
* John M. suggests:
* You might want to add four more:
*
* _ohdraw_ (optional head-end arrow for edges)
* _ohldraw_ (optional head-end label for edges)
* _otdraw_ (optional tail-end arrow for edges)
* _otldraw_ (optional tail-end label for edges)
*
* that would be generated when an additional option is supplied to
* dot, etc. and
* these would be the arrow/label positions to use if a user want to flip the
* direction of an edge (as sometimes is there want).
*/
static void
extend_attrs (graph_t* g)
{
int i;
bezier bz;
double scale;
double theta;
node_t* n;
edge_t* e;
attrsym_t* n_draw = NULL;
attrsym_t* n_l_draw = NULL;
attrsym_t* e_draw = NULL;
attrsym_t* h_draw = NULL;
attrsym_t* t_draw = NULL;
attrsym_t* e_l_draw = NULL;
attrsym_t* hl_draw = NULL;
attrsym_t* tl_draw = NULL;
unsigned char buf[BUFSIZ];
unsigned char cbuf[BUFSIZ];
if (GD_label(g))
g_l_draw = safe_dcl(g,g,"_ldraw_","",agraphattr);
if (GD_n_cluster(g))
g_draw = safe_dcl(g,g,"_draw_","",agraphattr);
n_draw = safe_dcl(g,g->proto->n,"_draw_","",agnodeattr);
n_l_draw = safe_dcl(g,g->proto->n,"_ldraw_","",agnodeattr);
e_draw = safe_dcl(g,g->proto->e,"_draw_","",agedgeattr);
if (e_arrows)
h_draw = safe_dcl(g,g->proto->e,"_hdraw_","",agedgeattr);
if (s_arrows)
t_draw = safe_dcl(g,g->proto->e,"_tdraw_","",agedgeattr);
if (GD_has_edge_labels(g) & EDGE_LABEL)
e_l_draw = safe_dcl(g,g->proto->e,"_ldraw_","",agedgeattr);
if (GD_has_edge_labels(g) & HEAD_LABEL)
hl_draw = safe_dcl(g,g->proto->e,"_hldraw_","",agedgeattr);
if (GD_has_edge_labels(g) & TAIL_LABEL)
tl_draw = safe_dcl(g,g->proto->e,"_tldraw_","",agedgeattr);
xbinit(&outbuf, BUFSIZ, buf);
xbinit(&charbuf, BUFSIZ, cbuf);
for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
if (ND_shape(n) && !isInvis(late_string(n,N_style,""))) {
ND_shape(n)->codefn(n);
agxset (n, n_draw->index, xbuse(&outbuf));
agxset (n, n_l_draw->index, xbuse(&charbuf));
}
if (State < GVSPLINES) continue;
for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
if (ED_edge_type(e) == IGNORED) continue;
if (isInvis(late_string(e,E_style,""))) continue;
if (ED_spl(e) == NULL) continue;
scale = late_double(e,E_arrowsz,1.0,0.0);
for (i = 0; i < ED_spl(e)->size; i++) {
bz = ED_spl(e)->list[i];
xd_bezier(bz.list,bz.size,FALSE,FALSE);
}
agxset (e, e_draw->index, xbuse(&outbuf));
for (i = 0; i < ED_spl(e)->size; i++) {
if (bz.sflag) {
/* dds: open style has sp == list[0] */
if ((bz.sflag == ARR_OPEN) || (bz.sflag == ARR_HALFOPEN))
theta = atan2pt(bz.list[1],bz.list[0]);
else
theta = atan2pt(bz.list[0],bz.sp);
arrow_gen(bz.sp, DEGREES(theta), scale, bz.sflag);
agxset (e, t_draw->index, xbuse(&outbuf));
}
if (bz.eflag) {
/* dds: open style has ep == list[size-1] */
if ((bz.eflag == ARR_OPEN) || (bz.eflag == ARR_HALFOPEN))
theta = atan2pt(bz.list[bz.size-2],bz.list[bz.size-1]);
else
theta = atan2pt(bz.list[bz.size-1],bz.ep);
arrow_gen(bz.ep, DEGREES(theta), scale, bz.eflag);
agxset (e, h_draw->index, xbuse(&outbuf));
}
}
if (ED_label(e)) {
emit_label(ED_label(e),e->tail->graph);
if (mapbool(late_string(e,E_decorate,"false")) && ED_spl(e)) {
emit_attachment(ED_label(e),ED_spl(e));
xbput (&charbuf, xbuse (&outbuf));
}
agxset (e, e_l_draw->index, xbuse(&charbuf));
}
if (ED_head_label(e)) {
emit_label(ED_head_label(e),e->tail->graph);
agxset (e, hl_draw->index, xbuse(&charbuf));
}
if (ED_tail_label(e)) {
emit_label(ED_tail_label(e),e->tail->graph);
agxset (e, tl_draw->index, xbuse(&charbuf));
}
}
}
if (GD_label(g)) {
emit_label(GD_label(g),g);
agxset (g, g_l_draw->index, xbuse(&charbuf));
}
emit_clusters (g,0);
xbfree(&outbuf);
xbfree(&charbuf);
}
|