/* This is an attempt at creating organisational presentation. The goal is to create appealing graphs, just focussing on the links between the objects. We originally used plantuml with some success, then graphviz to create nicer presentation. plantuml uses graphviz to render. Unfortunatly, graphviz is not really meant for that kind of graph. So a lot of tweaking is needed to get proper alignment. (invisible links for example). So here it is. First we define all objects in a single line. The syntax goes like this. We assume that a graph is made of groups. A group is made of objects organised in a hierarchy. The following is an example x/xx/xxx This shows a graph with one object on top, two objects below and 3 further down. The X is something and it is defined elsewhere. Objects are represented by a single letter. So we can have this S/ss/SSS One server talking to two services talking to 3 servers. Several groups may be represented using comma X/XX/XXXX,X/XX,XXX/XXX Parentheses may be used to define sub-groups X/Y(ss)Y(ss) X/Y(s/ss)Y(ss) All components will be named. When setting relation, we use the auto-defined named. For example, we have a proxy (P), spreading the load to a server cluster (S), in turn talking to a database (D). P/SSS/D Then we show the connections P1 -> S1 P1 -> S2 P1 -> S3 S1 -> D1 S2 -> D1 S3 -> D1 TODO: Draw arrows Lines go around object instead of straight. step: accept a tag instead of simply a number. When drawing a to:, h=tag is used to indicate when the line must be erased. Instead of scale down object, we scale up the containers */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" using namespace std; static int h_spacing = 10; // Spacing between objects horizontal static int v_spacing = 20; // vertical static int font_size = 20; static int arrow_margin = 10; static DEBUG_KEY D_GRAPH ("graph","Layout debug"); // String constant static string p_name("name"); static string p_left("left"); static string p_tleft("tleft"); static string p_bleft("bleft"); static string p_top("top"); static string p_bottom("bottom"); static string p_right("right"); static string p_tright("tright"); static string p_bright("bright"); static string p_empty(""); static string p_a("a"); static string p_c("c"); static string p_h("h"); static string p_t("t"); static string p_w("w"); static string p_fhandle("fhandle"); static string p_thandle("thandle"); static string p_dash("dash"); static string p_b("b"); static bool graph_finddoc (const char *fname, const vector &includedirs, string &res) { bool ret = false; for (unsigned i=0; i handles; OBJDEF(){ width = 1; height = 1; } OBJDEF(const string &_visual, const string &_name, int _width, int _height, const string &_fillcolor, const string &_strokecolor){ visual = _visual; name = _name; width = _width; height = _height; fillcolor = _fillcolor; strokecolor = _strokecolor; } }; struct BASEGROUP{ virtual ~BASEGROUP(){}; virtual BASEGROUP* dup() const = 0; virtual void print (double,double,double,const vector& includedirs, FILE *) = 0; virtual void assign (map &defs, map &idmap) = 0; virtual void layout ()=0; virtual void getsize(int &width, int &height) const =0; virtual void setvisible (bool) = 0; virtual void setlabelvisible (bool) = 0; }; class OBJ{ public: string username; // Name given by name: directive char letter; int id; double x; // Location inside the group double y; double scalex; // Scaled location within drawing double scaley; double scale; bool visible; bool labelvisible; OBJDEF def; BASEGROUP *sub; private: void copy (const OBJ &o){ username = o.username; letter = o.letter; id = o.id; x = o.x; y = o.y; scale = o.scale; scalex = o.scalex; scaley = o.scaley; def = o.def; visible = o.visible; labelvisible = o.labelvisible; } void init(){ id = -1; x = y = 0; scale = scalex = scaley = 0; visible = true; labelvisible = false; } public: OBJ(){ letter = ' '; sub = NULL; init(); } OBJ(const char _letter){ letter = _letter; sub = NULL; init(); } OBJ(const char _letter, const BASEGROUP &_g){ letter = _letter; sub = _g.dup(); init(); } OBJ & operator = (const OBJ &o){ copy(o); delete sub; sub = NULL; if (o.sub != NULL) sub = o.sub->dup(); return *this; } OBJ(const OBJ &o){ copy (o); sub = NULL; if (o.sub != NULL) sub = o.sub->dup(); } void clear(){ username.clear(); letter = ' '; delete sub; sub = NULL; init(); } ~OBJ(){ clear(); } void setdef (const OBJDEF &_def){ def = _def; } void setid (const int _id){ id = _id; } void setpos (double _x, double _y){ x = _x; y = _y; } void print (double offx, double offy, double _scale, const vector &includedirs, FILE *fout) { if (sub != NULL){ sub->print (offx,offy,_scale,includedirs,fout); } } void assign (map &defs, map &idmap){ map::iterator it = defs.find (letter); if (it == defs.end()){ tlmp_error ("Unknown letter %c\n",letter); }else{ setdef (it->second); setid (++idmap[letter]); } if (sub != NULL) sub->assign (defs,idmap); } void layout (){ if (sub != NULL) sub->layout (); if (def.width == 0 || def.height == 0){ int w,h; sub->getsize(w,h); if (def.width == 0) def.width = w; if (def.height == 0) def.height = h; } } void getsize(int &width, int &height) const { height = 0; width = 0; if (sub != NULL) sub->getsize(width,height); } void setvisible (bool _visible, bool child){ visible = _visible; // When we make a container invisible, all its content goes invisible // When we make it visible, we have to explicitly show the content if (child && sub != NULL) sub->setvisible(_visible); } void setlabelvisible (bool _visible){ labelvisible = _visible; } void setusername (const string &name){ username = name; } }; struct LINE { vector objs; void add (const char letter){ objs.push_back(OBJ(letter)); } void add (const char letter, const BASEGROUP &g){ objs.push_back(OBJ(letter,g)); } void clear(){ objs.clear(); } }; struct GROUP: public BASEGROUP { OBJ obj; int width; int height; vector lines; void add (const LINE &line){ lines.push_back(line); } void clear(){ obj.clear(); lines.clear(); } unsigned size(){ return lines.size(); } GROUP(){ width = 0; height = 0; } ~GROUP(){ clear(); } BASEGROUP *dup() const { GROUP *g = new GROUP; g->obj = obj; g->lines = lines; return g; } void print (double offx, double offy, double scale, const vector &includedirs, FILE *fout); // Assign OBJDEF to each object and also assign a numerical ID void assign (map &defs, map &idmap){ for (unsigned j=0; j 0) curx += h_spacing; curx += add; o.setpos (curx,y); o.layout (); if (maxh < o.def.height) maxh = o.def.height; curx += o.def.width; } y += maxh + v_spacing; } width = maxw; height = y; } void getsize(int &owidth, int &oheight) const{ owidth = width; oheight = height; } void setvisible (bool visible){ for (unsigned j=0; j void GROUP::print (double offx, double offy, double scale, const vector &includedirs, FILE *fout) { for (unsigned j=0; j\n" ,ox,oy,owidth,oheight,fillcolor,strokecolor); }else if (o.def.visual == "line"){ fprintf (fout,"\t\n" ,ox,oy+oheight,ox+owidth,oy+oheight ,strokecolor,2); }else if (o.def.visual == "circle"){ double w2 = owidth/2.0; fprintf (fout,"\t\n" ,ox+w2,oy+w2,w2,fillcolor,strokecolor); text_anchor = "middle"; tx = ox + owidth/2; }else if (o.def.visual == "ellipse"){ double w2 = owidth/2.0; double h2 = oheight/2.0; fprintf (fout,"\t\n" ,ox+w2,oy+h2,w2,h2,fillcolor,strokecolor); text_anchor = "middle"; tx = ox + owidth/2; }else if (o.def.visual.find(".s")!=string::npos){ // This is a svg template string tmp; if (graph_finddoc (o.def.visual.c_str(),includedirs,tmp)){ glocal FILE *fout = fout; glocal double offx = ox; glocal double offy = oy; glocal double w = owidth; glocal double h = oheight; glocal OBJDEF *def = &o.def; debug_printf ("Expanding %s\n",tmp.c_str()); (tmp.c_str(),true); if (line[0] != '#'){ string res = math_patch (line,glocal.offx,glocal.offy,glocal.w,glocal.h); const char *ptres = res.c_str(); if (strncmp(ptres,"handle:",7)==0){ vector tb; int n = str_splitline (ptres+7,' ',tb); if (n != 4){ tlmp_error ("Invalid handle definition: %s\n",ptres); }else{ glocal.def->handles[tb[0]] = COOR(atof(tb[1].c_str()),atof(tb[2].c_str()),tb[3]); } }else{ fprintf (glocal.fout,"%s\n",res.c_str()); } debug_printf ("math_patch %s\n",res.c_str()); } return 0; }else{ tlmp_error ("Can't find svg object %s\n",o.def.visual.c_str()); } }else if (o.def.visual != "filler"){ tlmp_error ("visual %s not done\n",o.def.visual.c_str()); } if (o.def.visual != "filler"){ map::iterator it = o.def.handles.find(p_name); double ty = oy-4; if (it != o.def.handles.end()){ text_anchor = it->second.anchor.c_str(); tx = it->second.x; ty = it->second.y; } if (o.labelvisible){ fprintf (fout,"\t\n" ,tx,ty,font_size,text_anchor); if (o.username.size() > 0){ fprintf (fout, "%s\n\n",o.username.c_str()); }else{ fprintf (fout, "%s%d\n\n",o.def.name.c_str(),o.id); } } } } o.scalex = ox; o.scaley = oy; o.scale = scale; int subw,subh; o.getsize(subw,subh); if (subw > 0 && subh > 0){ // We must fit the sub-group into this object. We have to compute // the new scale, combined with the scale parameter. // We have to either use the horizontal or vertical to decide // the scale so it fits. double wscale = (double)o.def.width/(subw+h_spacing); double hscale = (double)o.def.height/(subh+v_spacing); double subscale; debug_printf (D_GRAPH,"print sub subw=%d subh=%d width=%d height=%d wscale=%lf hscale=%lf\n" ,subw,subh,o.def.width,o.def.height,wscale,hscale); if (wscale < hscale){ subscale = wscale*scale; }else{ subscale = hscale*scale; } o.print (ox+h_spacing*subscale,oy+v_spacing*subscale,subscale,includedirs,fout); }else{ o.print (ox+h_spacing*scale,oy+v_spacing*scale,scale,includedirs,fout); } } } } static void layout_parse (const char *&s, GROUP &g) { while (1){ s = str_skip(s); if (*s == '\0'){ break; }else if (!isalpha(*s)){ break; } LINE line; while (isalpha(*s) || *s == '_'){ char letter = *s++; if (*s == '('){ s++; GROUP gg; layout_parse (s,gg); if (*s == ')'){ if (gg.size() > 0){ line.add (letter,gg); }else{ line.add (letter); } s++; }else{ tlmp_error ("Expect a ), got %s\n",s); return; } }else{ line.add (letter); } } g.add (line); if (*s == '/'){ s++; }else{ break; } } } static void layout_parse (const char *s, vector &groups) { while (1){ GROUP g; layout_parse (s,g); if (g.size() > 0) groups.push_back (g); s = str_skip(s); if (*s == '\0'){ break; }else if (*s != ','){ tlmp_error ("Invalid layout, expect another group with a /: %s\n",s); break; } s++; } } struct CONNECT{ int level; // Level at which this line start to be shown bool arrow_end; // Arrow need at the end of the line bool arrow_start; // At the start of the line string obj1; string obj2; string color; string dashpattern; int arclevel; // This is a number representing how the line must // be drawn. 0 means a straight line // -1 means some arcing, int width; // Line width string text; // Text on the side of the line (not done yet) string fhandle; // from: handle to connect the line with obj1 string thandle; // to: handle to connect the line with obj2 int hide; // This line will be hidden when we reach this level. CONNECT(int _level, int _hide, const string &_obj1, const string &_obj2, int _arclevel, int _width, const string &_color, const string &_dashpattern, const string &_text, const string &_fhandle, const string &_thandle){ arrow_end = true; arrow_start = false; level = _level; hide = _hide; obj1 = _obj1; obj2 = _obj2; text = _text; color = _color; dashpattern = _dashpattern; arclevel = _arclevel; width = _width; fhandle = _fhandle; thandle = _thandle; } }; struct SHOWCTRL{ int level; // Level at which this object become either visible or invisible bool label; // Affect label visibility of hole object bool visible; vector names; SHOWCTRL (int _level, bool _label, bool _visible, const vector &_names){ level = _level; label = _label; visible = _visible; names = _names; } }; /* Find an OBJ in a group using either its long or short name. Return NULL if not found. */ static OBJ *find (GROUP &g, const string &name) { OBJ *ret = NULL; const char *ptname = name.c_str(); for (unsigned j=0; j &groups, const string &name) { OBJ *ret = NULL; for (unsigned i=0; i 0){ map::const_iterator it = o->def.handles.find(handle); if (it != o->def.handles.end()){ x = it->second.x; y = it->second.y; } } } static void drawarrow( double x1, double y1, double x2, double y2, int arclevel, const string &color, FILE *fout) { // Draw a simple triangle rotated based on the orientation of the line double angle = atan2 (y2-y1,x2-x1); angle -= arclevel * M_PI_2 / 6; double angle2 = angle+M_PI_2; double angle3 = angle-M_PI_2; fprintf (fout,"\t\n" ,x2,y2 ,x2+cos(angle2)*5,y2+sin(angle2)*5 ,x2+cos(angle)*10,y2+sin(angle)*10 ,x2+cos(angle3)*5,y2+sin(angle3)*5 ,color.c_str()); } static void printline ( FILE *fout, bool highlit, int arclevel, int width, bool arrow_start, bool arrow_end, const string &color, const string &dashpattern, const OBJ *o1, // Top object const OBJ *o2, // Bottom const string &handle1, // handle on object o1 or "" const string &handle2) // idem for object o2 { debug_printf (D_GRAPH,"printline %d %d arclevel=%d color=%s\n",o1->visible,o2->visible,arclevel,color.c_str()); if (o1->visible && o2->visible){ #if 0 fprintf (stderr,"printline %lf %lf %lf -> %lf %lf %lf\n" ,o1->scalex,o1->scaley,o1->scale ,o2->scalex,o2->scaley,o2->scale); #endif double x1 = o1->scalex + (o1->def.width/2.0)*o1->scale; double y1 = o1->scaley + o1->def.height*o1->scale; usehandle (o1,handle1,x1,y1); double x2 = o2->scalex + (o2->def.width/2.0)*o2->scale; double y2 = o2->scaley; usehandle (o2,handle2,x2,y2); double midx1,midx2; double midy1,midy2; if (0 && arclevel != 0){ double offx = arclevel * arrow_margin; x1 += offx; x2 += offx; } if (x2 == x1){ // Vertical line if (arrow_start) y1 += arrow_margin; if (arrow_end) y2 -= arrow_margin; double diffy_4 = (y2-y1)/4; midy1 = y1 + diffy_4; midy2 = y1 + 3*diffy_4; midx1 = x1 + arclevel*arrow_margin; midx2 = midx1; }else{ double lenx = x2-x1; double leny = y2-y1; double len = sqrt(lenx*lenx+leny*leny); double sint = leny/len; double cost = lenx/len; double margx = cost * arrow_margin; double margy = sint * arrow_margin; if (arrow_start){ x1 += margx; y1 += margy; } if (arrow_end){ x2 -= margx; y2 -= margy; } double len_4 = len/4; midx1 = x1 + cost * len_4 + arclevel * arrow_margin; midy1 = y1 + sint * len_4; midx2 = x1 + cost * 3 * len_4 + arclevel * arrow_margin; midy2 = y1 + sint * 3 * len_4; debug_printf ("x1 %lf x2 %lf midx1 %lf midx2 %lf cost %lf len %lf arclevel %d arrow_margin %d\n" ,x1,x2,midx1,midx2,cost,len,arclevel,arrow_margin); debug_printf ("y1 %lf y2 %lf midy1 %lf midy2 %lf sint %lf\n",y1,y2,midy1,midy2,sint); } string dashstr; if (dashpattern.size() > 0){ dashstr = "stroke-dasharray=\"" + dashpattern + "\""; } if (arclevel == 0){ fprintf (fout,"\t\n" ,x1,y1,x2,y2 ,highlit ? "red" : color.c_str(),width,dashstr.c_str()); }else{ fprintf (fout,"\t\n" ,x1,y1,midx1,midy1,midx2,midy2,x2,y2 ,highlit ? "red" : color.c_str(),width,dashstr.c_str()); } // Draw the arrows if (arrow_start){ //fprintf (fout,"\t\n",x1,y1,width,color.c_str()); drawarrow(x2,y2,x1,y1,arclevel,color,fout); } if (arrow_end){ //fprintf (fout,"\t\n",x2,y2,width,color.c_str()); drawarrow(x1,y1,x2,y2,-arclevel,color,fout); } } } /* Parse var=value in args[] and place it in vals. vals already contains all default values, so we do not add any entries in vals. Return -1 if any error */ static int parseargs (const vector &args, map &vals) { int ret = 0; for (unsigned i=0; i::iterator it = vals.find(parm); if (it == vals.end()){ ret = -1; break; }else{ it->second = pt+1; } } } return ret; } /* Convert a set of path separated by : */ static void cnvinclude (const char *dirs, vector &tb) { const char *start = dirs; while (*dirs != '\0'){ if (*dirs == ':'){ tb.push_back(string(start,dirs-start)); dirs++; start = dirs; } dirs++; } if (dirs > start) tb.push_back(start); } struct NAME{ string def; string username; NAME(const string &_def, const string &_username){ def = _def; username = _username; } }; int main (int argc, char *argv[]) { glocal int ret = -1; glocal const char *output = "-"; glocal const char *includedir = "./"; glocal vector errors; glocal.ret = (argc,argv); setarg ('o',"output","SVG output file",glocal.output,false); presentation_setarg (this); navigation_setarg (this); setgrouparg ("Misc."); setarg (' ',"include","Directory for include files",glocal.includedir,false); fprintf (stderr,"%s",msg); glocal.errors.push_back(msg); int ret = -1; if (argc != 1){ usage(); }else{ glocal int level = 1; glocal int nbsteps = 0; glocal const char *fname = argv[0]; glocal map defs; glocal vector groups; glocal vector connects; glocal vector showctrl; // Control which object are visible or not glocal string title; glocal vector includedirs; glocal vector comments; glocal vector names; cnvinclude (glocal.includedir,glocal.includedirs); (argv[0],true); const char *invalid = NULL; if (strncmp(line,"layout:",7)==0){ layout_parse (line+7,glocal.groups); }else if (strncmp(line,"include:",8)==0){ const char *f = str_skip(line+8); if (f[0] == '/'){ include(f); }else{ string tmp; if (graph_finddoc (f,glocal.includedirs,tmp)){ include (tmp.c_str()); }else{ tlmp_error ("Can't find include file %s\n",f); } } }else if (strncmp(line,"title:",6)==0){ glocal.title = str_skip(line+6); }else if (strncmp(line,"spacing:",8)==0){ vector tb; int n = str_splitline (line+8,' ',tb); if (n == 2){ h_spacing = atoi(tb[0].c_str()); v_spacing = atoi(tb[1].c_str()); }else{ invalid = "spacing"; } }else if (strncmp(line,"fontsize:",9)==0){ vector tb; int n = str_splitline (line+9,' ',tb); if (n == 1){ font_size = atoi(tb[0].c_str()); }else{ invalid = "fontsize"; } }else if (strncmp(line,"def:",4)==0){ vector tb; int n = str_splitline (line+4,' ',tb); if (n == 6 || n == 7){ if (n == 6) tb.push_back("blue"); glocal.defs[tb[0][0]] = OBJDEF(tb[1],tb[2],atoi(tb[3].c_str()),atoi(tb[4].c_str()),tb[5],tb[6]); }else{ invalid = "def"; } }else if (strncmp(line,"to:",3)==0 || strncmp(line,"re:",3)==0){ vector tb; int n = str_splitline (line+3,' ',tb); if (n >= 2){ map vals; vals[p_a] = "0"; vals[p_b] = "0"; vals[p_w] = "2"; vals[p_c] = "black"; vals[p_t] = p_empty; vals[p_h] = "-1"; vals[p_fhandle] = p_empty; vals[p_thandle] = p_empty; vals[p_dash] = p_empty; vector args(tb.begin()+2,tb.end()); if (parseargs (args,vals) == -1){ invalid = "to"; }else{ int hide = atoi(vals[p_h].c_str()); if (hide != -1) hide += glocal.level; // Hide is relative to this level int arclevel = atoi(vals[p_a].c_str()); int width = atoi(vals[p_w].c_str()); string color = vals[p_c]; string text = vals[p_t]; string dash = vals[p_dash]; CONNECT con (glocal.level,hide,tb[0],tb[1],arclevel,width,color,dash,text,vals[p_fhandle],vals[p_thandle]); if (vals[p_b] == "1") con.arrow_start = true; if (strncmp(line,"re:",3)==0) con.arrow_end = false; glocal.connects.push_back (con); } }else{ invalid = "to"; } }else if (strncmp(line,"show:",5)==0 || strncmp(line,"hide:",5)==0){ vector tb; str_splitline (line+5,' ',tb); glocal.showctrl.push_back (SHOWCTRL(glocal.level,false,strncmp(line,"show:",5)==0,tb)); }else if (strncmp(line,"showt:",6)==0 || strncmp(line,"hidet:",6)==0){ vector tb; str_splitline (line+6,' ',tb); glocal.showctrl.push_back (SHOWCTRL(glocal.level,true,strncmp(line,"showt:",6)==0,tb)); }else if (strncmp(line,"step:",5)==0){ vector tb; int n = str_splitline (line+5,' ',tb); if (n == 1){ glocal.level = atoi(tb[0].c_str()); if (glocal.level > glocal.nbsteps) glocal.nbsteps = glocal.level; }else{ invalid = "step"; } }else if (strncmp(line,"name:",5)==0){ vector tb; int n = str_splitline (line+5,' ',tb); if (n == 2){ glocal.names.push_back(NAME(tb[0],tb[1])); }else{ invalid = "name"; } }else if (line[0] != '#'){ const char *end = str_skip(line); if (*end != '\0'){ tlmp_error ("Invalid line %d: %s\n",noline,line); } }else{ glocal.comments.push_back(str_skip(line+1)); } if (invalid != NULL){ tlmp_error ("Invalid \"%s\" statement, file %s, line %d: %s\n",invalid,info.filename,noline+1,line); } return 0; tlmp_error ("Missing file %s\n",fname); navigation_setsteps(glocal.nbsteps); glocal int highlit = navigation_gethighlit(); glocal int nbshow = navigation_getnbshow(); if (navigation_getshowtitle()){ printf ("%s\n",glocal.title.c_str()); exit (0); }else if (navigation_getcomments()){ for (unsigned i=0; i%s\n",glocal.comments[i].c_str()); } exit (0); } if (glocal.errors.size() == 0){ { // Associate items to OBJDEF and assign IDs map assign; for (unsigned i=0; isetusername(n.username); }else{ tlmp_error ("Can't find object named %s, can't set username to %s\n" ,n.def.c_str(),n.username.c_str()); } } } // Set the coordinates of the objects glocal int height = 0; glocal int width = 0; // Compute the width and height of the graph { int x = 1; for (unsigned i=0; i 0){ // make everything visible and let the show/hide statement // do their job in order for (unsigned i=0; isetlabelvisible(c.visible); }else{ o->setvisible(c.visible,child); } } } } } } } // Assign an arc to each line/relation #if 0 { map common_lines; // how many lines connect two objects for (unsigned i=0; iletter,o1->id,o2->letter,o2->id); common_lines[key]++; } } if (glocal.errors.size()==0){ // Now we know how many lines are connecting the same object for (unsigned i=0; iletter,o1->id,o2->letter,o2->id); int nb = common_lines[key]; } } } #endif // Print the graph if (glocal.errors.size() == 0){ (glocal.output,false); fprintf (fout,"\n"); fprintf (fout,"\n"); fprintf (fout,"\n"); fprintf (fout,"\n"); navigation_print (glocal.fname,glocal.nbsteps); util_format_title (glocal.title.c_str()); fprintf (fout,"
\n"); fprintf (fout,"
\n"); fputs ("\n",fout); fputs ("\n",fout); fprintf (fout,"\n" ,glocal.width,glocal.height,util_get_viewwidth(),util_get_viewheight()-util_get_titleheight()); fprintf (fout,"Produced by the nocracks graph-svg utility from file %s\n" ,glocal.fname); int x = 1; // Print the objects for (unsigned i=0; i= c.level && (c.hide == -1 || c.hide > glocal.nbshow)){ bool highlit = glocal.highlit == c.level; if (o1->scaley < o2->scaley){ // o1 higher than o2 printline (fout,highlit,c.arclevel,c.width,c.arrow_start,c.arrow_end,c.color,c.dashpattern,o1,o2,c.fhandle,c.thandle); }else{ printline (fout,highlit,c.arclevel,c.width,c.arrow_end,c.arrow_start,c.color,c.dashpattern,o2,o1,c.thandle,c.fhandle); } } } fputs ("\n",fout); fputs ("
\n",fout); fputs ("\n",fout); fputs ("\n",fout); return 0;
} } } if (glocal.errors.size() > 0){ (glocal.output,false); fprintf (fout,"\n"); fprintf (fout,"\n"); fprintf (fout,"\n"); fprintf (fout,"\n"); fprintf (fout,"

Some errors


\n"); for (unsigned i=0; i%s\n",glocal.errors[i].c_str()); } fputs ("\n",fout); fputs ("\n",fout); return 0;
ret = -1; } return ret;
return glocal.ret; }