/// @file /// @ingroup common_render /************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at https://graphviz.org *************************************************************************/ #include #include #include #include #include #include #include #include /// state for offset calculations typedef struct { double Y; ///< ymin + ymax double YF; ///< `Y` in inches } offsets_t; static double YFDIR(offsets_t offsets, double y) { return Y_invert ? offsets.YF - y : y; } double yDir(double y, double Y_off) { return Y_invert ? Y_off - y : y; } static void agputs(int (*putstr)(void *chan, const char *str), const char* s, FILE* fp) { putstr(fp, s); } static void agputc(int (*putstr)(void *chan, const char *str), char c, FILE *fp) { char buf[] = {c, '\0'}; putstr(fp, buf); } static void printstring(int (*putstr)(void *chan, const char *str), FILE *f, char *prefix, char *s) { if (prefix) agputs(putstr, prefix, f); agputs(putstr, s, f); } static void printint(int (*putstr)(void *chan, const char *str), FILE *f, char *prefix, size_t i) { agxbuf buf = {0}; if (prefix) agputs(putstr, prefix, f); agxbprint(&buf, "%" PRISIZE_T, i); agputs(putstr, agxbuse(&buf), f); agxbfree(&buf); } static void printdouble(int (*putstr)(void *chan, const char *str), FILE *f, char *prefix, double v) { agxbuf buf = {0}; if (prefix) agputs(putstr, prefix, f); agxbprint(&buf, "%.5g", v); agputs(putstr, agxbuse(&buf), f); agxbfree(&buf); } static void printpoint(int (*putstr)(void *chan, const char *str), FILE *f, pointf p, double yOff) { printdouble(putstr, f, " ", PS2INCH(p.x)); printdouble(putstr, f, " ", PS2INCH(yDir(p.y, yOff))); } /* setYInvert: * Set parameters used to flip coordinate system (y=0 at top). * Values do not need to be unset, since if Y_invert is set, it's * set for * all graphs during current run, so each will * reinitialize the values for its bbox. */ static offsets_t setYInvert(graph_t *g) { offsets_t rv = {0}; if (Y_invert) { rv.Y = GD_bb(g).UR.y + GD_bb(g).LL.y; rv.YF = PS2INCH(rv.Y); } return rv; } /* canon: * Canonicalize a string which may not have been allocated using agstrdup. */ static char* canon (graph_t *g, char* s) { char* ns = agstrdup (g, s); char* cs = agcanonStr (ns); agstrfree (g, ns); return cs; } static void writenodeandport(int (*putstr)(void *chan, const char *str), FILE *f, node_t *node, char *portname) { char *name; if (IS_CLUST_NODE(node)) name = canon (agraphof(node), strchr(agnameof(node), ':') + 1); else name = agcanonStr (agnameof(node)); printstring(putstr, f, " ", name); /* slimey i know */ if (portname && *portname) printstring(putstr, f, ":", agcanonStr(portname)); } void write_plain(GVJ_t *job, graph_t *g, FILE *f, bool extend) { char *tport, *hport; node_t *n; edge_t *e; bezier bz; pointf pt; char *lbl; char* fillcolor; int (*putstr)(void *chan, const char *str) = g->clos->disc.io->putstr; const offsets_t offsets = setYInvert(g); pt = GD_bb(g).UR; printdouble(putstr, f, "graph ", job->zoom); printdouble(putstr, f, " ", PS2INCH(pt.x)); printdouble(putstr, f, " ", PS2INCH(pt.y)); agputc(putstr, '\n', f); for (n = agfstnode(g); n; n = agnxtnode(g, n)) { if (IS_CLUST_NODE(n)) continue; printstring(putstr, f, "node ", agcanonStr(agnameof(n))); printpoint(putstr, f, ND_coord(n), offsets.Y); if (ND_label(n)->html) /* if html, get original text */ lbl = agcanonStr (agxget(n, N_label)); else lbl = canon(agraphof(n),ND_label(n)->text); printdouble(putstr, f, " ", ND_width(n)); printdouble(putstr, f, " ", ND_height(n)); printstring(putstr, f, " ", lbl); printstring(putstr, f, " ", late_nnstring(n, N_style, "solid")); printstring(putstr, f, " ", ND_shape(n)->name); printstring(putstr, f, " ", late_nnstring(n, N_color, DEFAULT_COLOR)); fillcolor = late_nnstring(n, N_fillcolor, ""); if (fillcolor[0] == '\0') fillcolor = late_nnstring(n, N_color, DEFAULT_FILL); printstring(putstr, f, " ", fillcolor); agputc(putstr, '\n', f); } for (n = agfstnode(g); n; n = agnxtnode(g, n)) { for (e = agfstout(g, n); e; e = agnxtout(g, e)) { if (extend) { //assuming these two attrs have already been created by cgraph if (!(tport = agget(e,"tailport"))) tport = ""; if (!(hport = agget(e,"headport"))) hport = ""; } else tport = hport = ""; if (ED_spl(e)) { size_t splinePoints = 0; for (size_t i = 0; i < ED_spl(e)->size; i++) { bz = ED_spl(e)->list[i]; splinePoints += bz.size; } printstring(putstr, f, NULL, "edge"); writenodeandport(putstr, f, agtail(e), tport); writenodeandport(putstr, f, aghead(e), hport); printint(putstr, f, " ", splinePoints); for (size_t i = 0; i < ED_spl(e)->size; i++) { bz = ED_spl(e)->list[i]; for (size_t j = 0; j < bz.size; j++) printpoint(putstr, f, bz.list[j], offsets.Y); } } if (ED_label(e)) { printstring(putstr, f, " ", canon(agraphof(agtail(e)),ED_label(e)->text)); printpoint(putstr, f, ED_label(e)->pos, offsets.Y); } printstring(putstr, f, " ", late_nnstring(e, E_style, "solid")); printstring(putstr, f, " ", late_nnstring(e, E_color, DEFAULT_COLOR)); agputc(putstr, '\n', f); } } agputs(putstr, "stop\n", f); } static void set_record_rects(node_t *n, field_t *f, agxbuf *xb, double yOff) { int i; if (f->n_flds == 0) { agxbprint(xb, "%.5g,%.5g,%.5g,%.5g ", f->b.LL.x + ND_coord(n).x, yDir(f->b.LL.y + ND_coord(n).y, yOff), f->b.UR.x + ND_coord(n).x, yDir(f->b.UR.y + ND_coord(n).y, yOff)); } for (i = 0; i < f->n_flds; i++) set_record_rects(n, f->fld[i], xb, yOff); } static void rec_attach_bb(graph_t *g, Agsym_t *bbsym, Agsym_t *lpsym, Agsym_t *lwsym, Agsym_t *lhsym, double yOff) { int c; agxbuf buf = {0}; pointf pt; agxbprint(&buf, "%.5g,%.5g,%.5g,%.5g", GD_bb(g).LL.x, yDir(GD_bb(g).LL.y, yOff), GD_bb(g).UR.x, yDir(GD_bb(g).UR.y, yOff)); agxset(g, bbsym, agxbuse(&buf)); if (GD_label(g) && GD_label(g)->text[0]) { pt = GD_label(g)->pos; agxbprint(&buf, "%.5g,%.5g", pt.x, yDir(pt.y, yOff)); agxset(g, lpsym, agxbuse(&buf)); pt = GD_label(g)->dimen; agxbprint(&buf, "%.2f", PS2INCH(pt.x)); agxset(g, lwsym, agxbuse(&buf)); agxbprint(&buf, "%.2f", PS2INCH(pt.y)); agxset(g, lhsym, agxbuse(&buf)); } for (c = 1; c <= GD_n_cluster(g); c++) rec_attach_bb(GD_clust(g)[c], bbsym, lpsym, lwsym, lhsym, yOff); agxbfree(&buf); } double attach_attrs_and_arrows(graph_t *g, bool *sp, bool *ep) { node_t *n; edge_t *e; pointf ptf; int dim3 = (GD_odim(g) >= 3); Agsym_t* bbsym = NULL; Agsym_t* lpsym = NULL; Agsym_t* lwsym = NULL; Agsym_t* lhsym = NULL; gv_fixLocale (1); bool e_arrows = false; // graph has edges with end arrows bool s_arrows = false; // graph has edges with start arrows const offsets_t offsets = setYInvert(g); agxbuf xb = {0}; safe_dcl(g, AGNODE, "pos", ""); safe_dcl(g, AGNODE, "rects", ""); N_width = safe_dcl(g, AGNODE, "width", ""); N_height = safe_dcl(g, AGNODE, "height", ""); safe_dcl(g, AGEDGE, "pos", ""); if (GD_has_labels(g) & NODE_XLABEL) safe_dcl(g, AGNODE, "xlp", ""); if (GD_has_labels(g) & EDGE_LABEL) safe_dcl(g, AGEDGE, "lp", ""); if (GD_has_labels(g) & EDGE_XLABEL) safe_dcl(g, AGEDGE, "xlp", ""); if (GD_has_labels(g) & HEAD_LABEL) safe_dcl(g, AGEDGE, "head_lp", ""); if (GD_has_labels(g) & TAIL_LABEL) safe_dcl(g, AGEDGE, "tail_lp", ""); if (GD_has_labels(g) & GRAPH_LABEL) { lpsym = safe_dcl(g, AGRAPH, "lp", ""); lwsym = safe_dcl(g, AGRAPH, "lwidth", ""); lhsym = safe_dcl(g, AGRAPH, "lheight", ""); } bbsym = safe_dcl(g, AGRAPH, "bb", ""); for (n = agfstnode(g); n; n = agnxtnode(g, n)) { if (dim3) { int k; agxbprint(&xb, "%.5g,%.5g,%.5g", ND_coord(n).x, yDir(ND_coord(n).y, offsets.Y), POINTS_PER_INCH * ND_pos(n)[2]); for (k = 3; k < GD_odim(g); k++) { agxbprint(&xb, ",%.5g", POINTS_PER_INCH*(ND_pos(n)[k])); } agset(n, "pos", agxbuse(&xb)); } else { agxbprint(&xb, "%.5g,%.5g", ND_coord(n).x, yDir(ND_coord(n).y, offsets.Y)); agset(n, "pos", agxbuse(&xb)); } agxbprint(&xb, "%.5g", PS2INCH(ND_ht(n))); agxset(n, N_height, agxbuse(&xb)); agxbprint(&xb, "%.5g", PS2INCH(ND_lw(n) + ND_rw(n))); agxset(n, N_width, agxbuse(&xb)); if (ND_xlabel(n) && ND_xlabel(n)->set) { ptf = ND_xlabel(n)->pos; agxbprint(&xb, "%.5g,%.5g", ptf.x, yDir(ptf.y, offsets.Y)); agset(n, "xlp", agxbuse(&xb)); } if (strcmp(ND_shape(n)->name, "record") == 0) { set_record_rects(n, ND_shape_info(n), &xb, offsets.Y); agxbpop(&xb); /* get rid of last space */ agset(n, "rects", agxbuse(&xb)); } else { polygon_t *poly; if (N_vertices && isPolygon(n)) { poly = ND_shape_info(n); size_t sides = poly->sides; if (sides < 3) { char *p = agget(n, "samplepoints"); if (p) sides = strtoul(p, NULL, 0); else sides = 8; if (sides < 3) sides = 8; } for (size_t i = 0; i < sides; i++) { if (i > 0) agxbputc(&xb, ' '); if (poly->sides >= 3) agxbprint(&xb, "%.5g %.5g", PS2INCH(poly->vertices[i].x), YFDIR(offsets, PS2INCH(poly->vertices[i].y))); else agxbprint(&xb, "%.5g %.5g", ND_width(n) / 2.0 * cos((double)i / (double)sides * M_PI * 2.0), YFDIR(offsets, ND_height(n) / 2.0 * sin((double)i / (double)sides * M_PI * 2.0))); } agxset(n, N_vertices, agxbuse(&xb)); } } if (State >= GVSPLINES) { for (e = agfstout(g, n); e; e = agnxtout(g, e)) { if (ED_edge_type(e) == IGNORED) continue; if (ED_spl(e) == NULL) continue; /* reported in postproc */ for (size_t i = 0; i < ED_spl(e)->size; i++) { if (i > 0) agxbputc(&xb, ';'); if (ED_spl(e)->list[i].sflag) { s_arrows = true; agxbprint(&xb, "s,%.5g,%.5g ", ED_spl(e)->list[i].sp.x, yDir(ED_spl(e)->list[i].sp.y, offsets.Y)); } if (ED_spl(e)->list[i].eflag) { e_arrows = true; agxbprint(&xb, "e,%.5g,%.5g ", ED_spl(e)->list[i].ep.x, yDir(ED_spl(e)->list[i].ep.y, offsets.Y)); } for (size_t j = 0; j < ED_spl(e)->list[i].size; j++) { if (j > 0) agxbputc(&xb, ' '); ptf = ED_spl(e)->list[i].list[j]; agxbprint(&xb, "%.5g,%.5g", ptf.x, yDir(ptf.y, offsets.Y)); } } agset(e, "pos", agxbuse(&xb)); if (ED_label(e)) { ptf = ED_label(e)->pos; agxbprint(&xb, "%.5g,%.5g", ptf.x, yDir(ptf.y, offsets.Y)); agset(e, "lp", agxbuse(&xb)); } if (ED_xlabel(e) && ED_xlabel(e)->set) { ptf = ED_xlabel(e)->pos; agxbprint(&xb, "%.5g,%.5g", ptf.x, yDir(ptf.y, offsets.Y)); agset(e, "xlp", agxbuse(&xb)); } if (ED_head_label(e)) { ptf = ED_head_label(e)->pos; agxbprint(&xb, "%.5g,%.5g", ptf.x, yDir(ptf.y, offsets.Y)); agset(e, "head_lp", agxbuse(&xb)); } if (ED_tail_label(e)) { ptf = ED_tail_label(e)->pos; agxbprint(&xb, "%.5g,%.5g", ptf.x, yDir(ptf.y, offsets.Y)); agset(e, "tail_lp", agxbuse(&xb)); } } } } rec_attach_bb(g, bbsym, lpsym, lwsym, lhsym, offsets.Y); agxbfree(&xb); if (HAS_CLUST_EDGE(g)) undoClusterEdges(g); if (sp != NULL) { *sp = s_arrows; } if (ep != NULL) { *ep = e_arrows; } gv_fixLocale (0); return offsets.Y; } void attach_attrs(graph_t * g) { (void)attach_attrs_and_arrows(g, NULL, NULL); }