/* 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: 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; jn_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); }