/misc/src/release/graphviz-2.18-1/src/graphviz-2.18/lib/common/htmltable.c

Go to the documentation of this file.
00001 /* $Id: htmltable.c,v 1.57 2008/03/06 21:40:02 ellson Exp $ $Revision: 1.57 $ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /**********************************************************
00005 *      This software is part of the graphviz package      *
00006 *                http://www.graphviz.org/                 *
00007 *                                                         *
00008 *            Copyright (c) 1994-2004 AT&T Corp.           *
00009 *                and is licensed under the                *
00010 *            Common Public License, Version 1.0           *
00011 *                      by AT&T Corp.                      *
00012 *                                                         *
00013 *        Information and Software Systems Research        *
00014 *              AT&T Research, Florham Park NJ             *
00015 **********************************************************/
00016 
00017 
00018 /* Implementation of HTML-like tables.
00019  * 
00020  * The CodeGen graphics model, especially with integral coodinates, is
00021  * not adequate to handle this as we would like. In particular, it is
00022  * difficult to handle notions of adjacency and correct rounding to pixels.
00023  * For example, if 2 adjacent boxes bb1.UR.x == bb2.LL.x, the rectangles
00024  * may be drawn overlapping. However, if we use bb1.UR.x+1 == bb2.LL.x
00025  * there may or may not be a gap between them, even in the same device
00026  * depending on their positions. When CELLSPACING > 1, this isn't as much
00027  * of a problem.
00028  *
00029  * We allow negative spacing as a hack to allow overlapping cell boundaries.
00030  * For the reasons discussed above, this is difficult to get correct.
00031  * This is an important enough case we should extend the table model to
00032  * support it correctly. This could be done by allowing a table attribute,
00033  * e.g., CELLGRID=n, which sets CELLBORDER=0 and has the border drawing
00034  * handled correctly by the table.
00035  */
00036 #include "render.h"
00037 #include "htmltable.h"
00038 #include "agxbuf.h"
00039 #include "pointset.h"
00040 #include <assert.h>
00041 
00042 #define DEFAULT_BORDER    1
00043 #define DEFAULT_CELLPADDING  2
00044 #define DEFAULT_CELLSPACING  2
00045 
00046 typedef struct {
00047     point p;
00048     htmlfont_t finfo;
00049     void *obj;
00050     graph_t *g;
00051     char* imgscale;
00052 } htmlenv_t;
00053 
00054 typedef struct {
00055     char *url; 
00056     char *tooltip;
00057     char *target;
00058     boolean explicit_tooltip;
00059     point LL;
00060     point UR;
00061 } htmlmap_data_t;
00062 
00063 #ifdef DEBUG
00064 static void printCell(htmlcell_t * cp, int ind);
00065 #endif
00066 
00067 /* pushFontInfo:
00068  * Replace current font attributes in env with ones from fp,
00069  * storing old attributes in savp. We only deal with attributes
00070  * set in env. The attributes are restored via popFontInfo.
00071  */
00072 static void
00073 pushFontInfo(htmlenv_t * env, htmlfont_t * fp, htmlfont_t * savp)
00074 {
00075     if (env->finfo.name) {
00076         if (fp->name) {
00077             savp->name = env->finfo.name;
00078             env->finfo.name = fp->name;
00079         } else
00080             savp->name = NULL;
00081     }
00082     if (env->finfo.color) {
00083         if (fp->color) {
00084             savp->color = env->finfo.color;
00085             env->finfo.color = fp->color;
00086         } else
00087             savp->color = NULL;
00088     }
00089     if (env->finfo.size >= 0) {
00090         if (fp->size >= 0) {
00091             savp->size = env->finfo.size;
00092             env->finfo.size = fp->size;
00093         } else
00094             savp->size = -1.0;
00095     }
00096 }
00097 
00098 /* popFontInfo:
00099  * Restore saved font attributes.
00100  * Copy only set values.
00101  */
00102 static void popFontInfo(htmlenv_t * env, htmlfont_t * savp)
00103 {
00104     if (savp->name)
00105         env->finfo.name = savp->name;
00106     if (savp->color)
00107         env->finfo.color = savp->color;
00108     if (savp->size >= 0.0)
00109         env->finfo.size = savp->size;
00110 }
00111 
00112 static void 
00113 emit_htextparas(GVJ_t* job, int nparas, htextpara_t* paras, pointf p,
00114          double halfwidth_x, char* fname, double fsize, char* fcolor, box b)
00115 {
00116     int i,j;
00117     double tmp, center_x, left_x, right_x, fsize_;
00118     double offset;
00119     char *fname_ , *fcolor_;
00120     textpara_t tl;
00121     pointf p_ = {0.0, 0.0};
00122     textitem_t* ti;
00123         
00124     center_x = p.x;
00125     left_x = center_x - halfwidth_x;
00126     right_x = center_x + halfwidth_x;
00127 
00128         /* Initial p is in center of text block; set initial baseline
00129          * to top of text block.
00130          */
00131     p_.y = p.y + (double)(b.UR.y-b.LL.y)/2.0;
00132     tmp = ROUND(p_.y);  /* align with integer points */
00133     p_.y = (double)tmp;
00134 
00135     gvrender_begin_context(job);
00136     for(i=0; i<nparas; i++) {
00137         switch (paras[i].just) {
00138         case 'l':
00139             p_.x = left_x;
00140             p.x = left_x;
00141             break;
00142         case 'r':
00143             p_.x = right_x;
00144             p.x = right_x;              
00145             break;
00146         default:
00147         case 'n':
00148             p_.x = center_x;
00149             p.x = center_x;
00150             break;
00151         }
00152         p_.y -= paras[i].lfsize;  /* move to current base line */
00153 
00154         ti = paras[i].items;
00155         offset = 0.0;
00156         for(j=0; j<paras[i].nitems; j++) {
00157             if (ti->font && (ti->font->size > 0))
00158                 fsize_ = ti->font->size;
00159             else
00160                 fsize_ = fsize;
00161             if (ti->font && ti->font->name)
00162                 fname_ = ti->font->name;
00163             else
00164                 fname_ = fname;
00165             if (ti->font && ti->font->color)
00166                 fcolor_ = ti->font->color;
00167             else
00168                 fcolor_ = fcolor;
00169 
00170             gvrender_set_pencolor(job, fcolor_);
00171             gvrender_set_font(job, fname_, fsize_);
00172 
00173             tl.str = ti->str;
00174             tl.fontname = fname_;
00175             tl.fontsize = fsize_;
00176             tl.yoffset_layout = ti->yoffset_layout;
00177             tl.yoffset_centerline = ti->yoffset_centerline;
00178             tl.postscript_alias = ti->postscript_alias;
00179             tl.layout = ti->layout;
00180             tl.width = paras[i].size;
00181             tl.height = paras[i].lfsize;
00182             tl.just = paras[i].just;
00183 
00184             gvrender_textpara(job, p_, &tl);
00185             offset += ti->size;
00186             p_.x = p.x + offset;
00187             ti++;
00188         }
00189     }
00190 
00191     gvrender_end_context(job);
00192 }
00193 
00194 static void
00195 emit_html_txt(GVJ_t* job, htmltxt_t* tp, htmlenv_t* env)
00196 {
00197     double halfwidth_x;
00198     pointf p;
00199     char *fname;
00200     char *fcolor;
00201     double fsize;
00202 
00203     /* make sure that there is something to do */
00204     if (tp->nparas < 1)
00205         return;
00206 
00207     fsize = env->finfo.size;
00208     fname = env->finfo.name;
00209     fcolor = env->finfo.color;
00210 
00211     halfwidth_x = ((double) (tp->box.UR.x - tp->box.LL.x)) / 2.0;
00212     p.x = env->p.x + ((double) (tp->box.UR.x + tp->box.LL.x)) / 2.0;
00213     p.y = env->p.y + ((double) (tp->box.UR.y + tp->box.LL.y)) / 2.0;
00214 
00215     emit_htextparas(job, tp->nparas, tp->paras, p, halfwidth_x, fname,
00216                     fsize, fcolor, tp->box);
00217 }
00218 
00219 static void doSide(GVJ_t * job, pointf p, double wd, double ht)
00220 {
00221     boxf BF;
00222 
00223     BF.LL = p;
00224     BF.UR.x = p.x + wd;
00225     BF.UR.y = p.y + ht;
00226     gvrender_box(job, BF, 1);
00227 }
00228 
00229 /* doBorder:
00230  * Draw rectangle of width border inside rectangle given
00231  * by box. If border is 1, we call use a single call to gvrender_polygon.
00232  * (We have set linewidth to 1 below.) Otherwise, we use four separate
00233  * filled rectangles. We could use a richer graphics model, as things
00234  * can go wrong when cell spacing and borders are small.
00235  * We decrement the border value by 1, as typically a filled rectangle
00236  * from x to x+border will all pixels from x to x+border, and thus have
00237  * width border+1.
00238  */
00239 static void doBorder(GVJ_t * job, char *color, int border, box B)
00240 {
00241     pointf pt;
00242     boxf BF;
00243     double wd, ht;
00244 
00245     gvrender_begin_context(job);
00246 
00247     if (!color)
00248         color = "black";
00249     gvrender_set_fillcolor(job, color);
00250     gvrender_set_pencolor(job, color);
00251 
00252     B2BF(B, BF);
00253     if (border == 1) {
00254         gvrender_box(job, BF, 0);
00255     } else {
00256         border--;
00257         ht = BF.UR.y - BF.LL.y;
00258         wd = BF.UR.x - BF.LL.x;
00259         doSide(job, BF.LL, border, ht);
00260         pt.x = BF.LL.x;
00261         pt.y = BF.UR.y;
00262         doSide(job, pt, wd, -border);
00263         doSide(job, BF.UR, -border, -ht);
00264         pt.x = BF.UR.x;
00265         pt.y = BF.LL.y;
00266         doSide(job, pt, -wd, border);
00267     }
00268 
00269     gvrender_end_context(job);
00270 }
00271 
00272 static void doFill(GVJ_t * job, char *color, box B)
00273 {
00274     boxf BF;
00275 
00276     gvrender_set_fillcolor(job, color);
00277     gvrender_set_pencolor(job, color);
00278     B2BF(B, BF);
00279     gvrender_box(job, BF, 1);
00280 }
00281 
00282 /* initAnchor:
00283  * Save current map values
00284  * Initialize fields in job->obj pertaining to anchors.
00285  * In particular, this also sets the output rectangle.
00286  * If there is something to do, close current anchor if
00287  * necesary, start the anchor and returns 1.
00288  * Otherwise, it returns 0.
00289  *
00290  * FIX: Should we provide a tooltip if none is set, as is done
00291  * for nodes, edges, etc. ?
00292  */
00293 static int
00294 initAnchor (GVJ_t* job, htmldata_t* data, box pts, htmlmap_data_t* save,
00295     int closePrev)
00296 {
00297     obj_state_t *obj = job->obj;
00298     int changed;
00299 
00300     save->url = obj->url; 
00301     save->tooltip = obj->tooltip;
00302     save->target = obj->target;
00303     save->explicit_tooltip = obj->explicit_tooltip;
00304     changed = initMapData (job, NULL, data->href, data->title, data->target, obj->u.g);
00305 
00306     if (changed) {
00307         if (closePrev && (save->url || save->explicit_tooltip))
00308             gvrender_end_anchor(job);
00309         if (obj->url || obj->explicit_tooltip) {
00310             emit_map_rect(job, pts.LL, pts.UR);
00311             gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
00312         }
00313     }
00314     return changed;
00315 }
00316 
00317 #define RESET(fld) \
00318   if(obj->fld != save->fld) {free(obj->fld); obj->fld = save->fld;}
00319 
00320 /* endAnchor:
00321  * Pop context pushed by initAnchor.
00322  * This is done by ending current anchor, restoring old values and
00323  * freeing new, and reopening previous anchor if necessary.
00324  *
00325  * NB: We don't save or restore geometric map info. This is because
00326  * this preservation of map context is only necessary for SVG-like
00327  * systems where graphical items are wrapped in an anchor, and we map
00328  * top-down. For ordinary map anchors, this is all done bottom-up, so
00329  * the geometric map info at the higher level hasn't been emitted yet.
00330  */
00331 static void
00332 endAnchor (GVJ_t* job, htmlmap_data_t* save, int openPrev)
00333 {
00334     obj_state_t *obj = job->obj;
00335 
00336     if (obj->url || obj->explicit_tooltip)
00337         gvrender_end_anchor(job);
00338     RESET(url);
00339     RESET(tooltip);
00340     RESET(target);
00341     obj->explicit_tooltip = save->explicit_tooltip;
00342     if (openPrev && (obj->url || obj->explicit_tooltip))
00343         gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
00344 }
00345 
00346 /* forward declaration */
00347 static void emit_html_cell(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env);
00348 
00349 static void
00350 emit_html_tbl(GVJ_t * job, htmltbl_t * tbl, htmlenv_t * env)
00351 {
00352     box pts = tbl->data.box;
00353     point p = env->p;
00354     htmlcell_t **cells = tbl->u.n.cells;
00355     htmlfont_t savef;
00356     htmlmap_data_t saved;
00357     int anchor; /* if true, we need to undo anchor settings. */
00358     int doAnchor = (tbl->data.href || tbl->data.target);
00359 
00360     if (tbl->font)
00361         pushFontInfo(env, tbl->font, &savef);
00362 
00363     pts.LL.x += p.x;
00364     pts.UR.x += p.x;
00365     pts.LL.y += p.y;
00366     pts.UR.y += p.y;
00367 
00368     if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST))
00369         anchor = initAnchor(job, &tbl->data, pts, &saved, 1);
00370     else
00371         anchor = 0;
00372 
00373     if (tbl->data.bgcolor)
00374         doFill(job, tbl->data.bgcolor, pts);
00375 
00376     if (tbl->data.border)
00377         doBorder(job, tbl->data.pencolor, tbl->data.border, pts);
00378 
00379     while (*cells) {
00380         emit_html_cell(job, *cells, env);
00381         cells++;
00382     }
00383 
00384     if (anchor)
00385         endAnchor (job, &saved, 1);
00386 
00387     if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) {
00388         if (initAnchor(job, &tbl->data, pts, &saved, 0))
00389             endAnchor (job, &saved, 0);
00390     }
00391 
00392     if (tbl->font)
00393         popFontInfo(env, &savef);
00394 }
00395 
00396 /* emit_html_img:
00397  * The image will be centered in the given box.
00398  * Scaling is determined by either the image's scale attribute,
00399  * or the imagescale attribute of the graph object being drawn.
00400  */
00401 static void
00402 emit_html_img(GVJ_t * job, htmlimg_t * cp, htmlenv_t * env)
00403 {
00404     pointf A[4];
00405     box bb = cp->box;
00406     char* scale;
00407 
00408     bb.LL.x += env->p.x;
00409     bb.LL.y += env->p.y;
00410     bb.UR.x += env->p.x;
00411     bb.UR.y += env->p.y;
00412 
00413     P2PF(bb.UR, A[0]);
00414     P2PF(bb.LL, A[2]);
00415     A[1].x = A[2].x;
00416     A[1].y = A[0].y;
00417     A[3].x = A[0].x;
00418     A[3].y = A[2].y;
00419 
00420     if (cp->scale)
00421         scale = cp->scale;
00422     else
00423         scale = env->imgscale;
00424     gvrender_usershape(job, cp->src, A, 4, TRUE, scale);
00425 }
00426 
00427 static void
00428 emit_html_cell(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env)
00429 {
00430     htmlmap_data_t saved;
00431     box pts = cp->data.box;
00432     point p = env->p;
00433     int inAnchor, doAnchor = (cp->data.href || cp->data.target);
00434 
00435     pts.LL.x += p.x;
00436     pts.UR.x += p.x;
00437     pts.LL.y += p.y;
00438     pts.UR.y += p.y;
00439 
00440     if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST))
00441         inAnchor = initAnchor(job, &cp->data, pts, &saved, 1);
00442     else
00443         inAnchor = 0;
00444 
00445     if (cp->data.bgcolor) {
00446         doFill(job, cp->data.bgcolor, pts);
00447     }
00448 
00449     if (cp->data.border)
00450         doBorder(job, cp->data.pencolor, cp->data.border, pts);
00451 
00452     if (cp->child.kind == HTML_TBL)
00453         emit_html_tbl(job, cp->child.u.tbl, env);
00454     else if (cp->child.kind == HTML_IMAGE)
00455         emit_html_img(job, cp->child.u.img, env);
00456     else
00457         emit_html_txt(job, cp->child.u.txt, env);
00458 
00459     if (inAnchor)
00460         endAnchor (job, &saved, 1);
00461 
00462     if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) {
00463         if (initAnchor(job, &cp->data, pts, &saved, 0))
00464             endAnchor (job, &saved, 0);
00465     }
00466 }
00467 
00468 /* allocObj:
00469  * Push new obj on stack to be used in common by all 
00470  * html elements with anchors.
00471  * This inherits the type, emit_state, and object of the
00472  * parent, as well as the url, explicit, target and tooltip.
00473  */
00474 static void
00475 allocObj (GVJ_t * job)
00476 {
00477     obj_state_t *obj;
00478     obj_state_t *parent;
00479 
00480     obj = push_obj_state(job);
00481     parent = obj->parent;
00482     obj->type = parent->type;
00483     obj->emit_state = parent->emit_state;
00484     switch (obj->type) {
00485     case NODE_OBJTYPE :
00486         obj->u.n = parent->u.n;
00487 #ifdef WITH_CODEGENS
00488         Obj = NODE;
00489 #endif
00490         break;
00491     case ROOTGRAPH_OBJTYPE :
00492         obj->u.g = parent->u.g;
00493 #ifdef WITH_CODEGENS
00494         Obj = NONE;
00495 #endif
00496         break;
00497     case CLUSTER_OBJTYPE :
00498         obj->u.sg = parent->u.sg;
00499 #ifdef WITH_CODEGENS
00500         Obj = CLST;
00501 #endif
00502         break;
00503     case EDGE_OBJTYPE :
00504         obj->u.e = parent->u.e;
00505 #ifdef WITH_CODEGENS
00506         Obj = EDGE;
00507 #endif
00508         break;
00509     }
00510     obj->url = parent->url;
00511     obj->tooltip = parent->tooltip;
00512     obj->target = parent->target;
00513     obj->explicit_tooltip = parent->explicit_tooltip;
00514 }
00515 
00516 static void
00517 freeObj (GVJ_t * job)
00518 {
00519     obj_state_t *obj = job->obj;
00520 
00521     obj->url = NULL;
00522     obj->tooltip = NULL;
00523     obj->target = NULL;
00524     pop_obj_state(job);
00525 }
00526 
00527 /* emit_html_label:
00528  */
00529 void
00530 emit_html_label(GVJ_t * job, htmllabel_t * lp, textlabel_t * tp)
00531 {
00532     htmlenv_t env;
00533 
00534     allocObj (job);
00535     env.p = tp->p;
00536     env.finfo.color = tp->fontcolor;
00537     env.finfo.name = tp->fontname;
00538     env.finfo.size = tp->fontsize;
00539     env.finfo.size = tp->fontsize;
00540     env.imgscale = agget (job->obj->u.n, "imagescale");
00541     if ((env.imgscale == NULL) || (*env.imgscale == '\0'))
00542         env.imgscale = "false";
00543     if (lp->kind == HTML_TBL) {
00544         htmltbl_t *tbl = lp->u.tbl;
00545 
00546         /* set basic graphics context */
00547         gvrender_begin_context(job);
00548         /* Need to override line style set by node. */
00549         gvrender_set_style(job, job->gvc->defaultlinestyle);
00550         if (tbl->data.pencolor)
00551             gvrender_set_pencolor(job, tbl->data.pencolor);
00552         else
00553             gvrender_set_pencolor(job, DEFAULT_COLOR);
00554         emit_html_tbl(job, tbl, &env);
00555         gvrender_end_context(job);
00556     } else {
00557         emit_html_txt(job, lp->u.txt, &env);
00558     }
00559     freeObj (job);
00560 }
00561 
00562 void free_html_font(htmlfont_t * fp)
00563 {
00564     fp->cnt--;
00565     if (fp->cnt == 0) {
00566         if (fp->name)
00567            free(fp->name);
00568         if (fp->color)
00569            free(fp->color);
00570         free(fp);
00571     }
00572 }
00573 
00574 void free_html_data(htmldata_t * dp)
00575 {
00576     free(dp->href);
00577     free(dp->port);
00578     free(dp->target);
00579     free(dp->title);
00580     free(dp->bgcolor);
00581 }
00582 
00583 void free_html_text(htmltxt_t* t)
00584 {
00585     htextpara_t *tl;
00586     textitem_t *ti;
00587     int i, j;
00588 
00589     if (!t) return;
00590 
00591     tl = t->paras;
00592     if (tl) {
00593         ti = tl->items;
00594         for (i = 0; i < t->nparas; i++) {
00595             for (j = 0; j < tl->nitems; j++) {
00596                 if (ti->str) free (ti->str);
00597                 if (ti->font) free_html_font(ti->font);
00598                 if (ti->layout && ti->free_layout) ti->free_layout (ti->layout);
00599                 ti++;
00600             }
00601             tl++;
00602             ti = tl->items;
00603         }
00604         if (ti != tl->items) free(tl->items);
00605         if (tl != t->paras) free(t->paras);
00606     }
00607     free(t);
00608 }
00609 
00610 void free_html_img(htmlimg_t * ip)
00611 {
00612     free(ip->src);
00613     free(ip);
00614 }
00615 
00616 static void free_html_cell(htmlcell_t * cp)
00617 {
00618     free_html_label(&cp->child, 0);
00619     free_html_data(&cp->data);
00620     free(cp);
00621 }
00622 
00623 /* free_html_tbl:
00624  * If tbl->n_rows is negative, table is in initial state from
00625  * HTML parse, with data stored in u.p. Once run through processTable,
00626  * data is stored in u.n and tbl->n_rows is > 0.
00627  */
00628 static void free_html_tbl(htmltbl_t * tbl)
00629 {
00630     htmlcell_t **cells;
00631 
00632     if (tbl->rc == -1) {
00633         dtclose(tbl->u.p.rows);
00634     } else {
00635         cells = tbl->u.n.cells;
00636 
00637         free(tbl->heights);
00638         free(tbl->widths);
00639         while (*cells) {
00640             free_html_cell(*cells);
00641             cells++;
00642         }
00643         free(tbl->u.n.cells);
00644     }
00645     if (tbl->font)
00646         free_html_font(tbl->font);
00647     free_html_data(&tbl->data);
00648     free(tbl);
00649 }
00650 
00651 void free_html_label(htmllabel_t * lp, int root)
00652 {
00653     if (lp->kind == HTML_TBL)
00654         free_html_tbl(lp->u.tbl);
00655     else if (lp->kind == HTML_IMAGE)
00656         free_html_img(lp->u.img);
00657     else
00658         free_html_text(lp->u.txt);
00659     if (root)
00660         free(lp);
00661 }
00662 
00663 static htmldata_t* portToTbl(htmltbl_t *, char *);  /* forward declaration */
00664 
00665 static htmldata_t* portToCell(htmlcell_t * cp, char *id)
00666 {
00667     htmldata_t* rv;
00668 
00669     if (cp->data.port && (strcasecmp(cp->data.port, id) == 0))
00670         rv = &cp->data;
00671     else if (cp->child.kind == HTML_TBL)
00672         rv = portToTbl(cp->child.u.tbl, id);
00673     else
00674         rv = NULL;
00675 
00676     return rv;
00677 }
00678 
00679 /* portToTbl:
00680  * See if tp or any of its child cells has the given port id.
00681  * If true, return corresponding box.
00682  */
00683 static htmldata_t* 
00684 portToTbl(htmltbl_t* tp, char* id)
00685 {
00686     htmldata_t*  rv;
00687     htmlcell_t** cells;
00688     htmlcell_t*  cp;
00689 
00690     if (tp->data.port && (strcasecmp(tp->data.port, id) == 0))
00691         rv = &tp->data;
00692     else {
00693         rv = NULL;
00694         cells = tp->u.n.cells;
00695         while ((cp = *cells++)) {
00696             if ((rv = portToCell(cp, id)))
00697                 break;
00698         }
00699     }
00700 
00701     return rv;
00702 }
00703 
00704 /* html_port:
00705  * See if edge port corresponds to part of the html node.
00706  * Assume pname != "".
00707  * If successful, return pointer to port's box.
00708  * Else return NULL.
00709  */
00710 box *html_port(node_t * n, char *pname, int* sides)
00711 {
00712     htmldata_t*   tp; 
00713     htmllabel_t* lbl = ND_label(n)->u.html;
00714     box*         rv = NULL;
00715 
00716     if (lbl->kind == HTML_TEXT)
00717         return NULL;
00718 
00719     tp = portToTbl(lbl->u.tbl, pname);
00720     if (tp) {
00721         rv = &tp->box;
00722         *sides = tp->sides;
00723     }
00724     return rv;
00725 
00726 }
00727 
00728 /* html_path:
00729  * Return a box in a table containing the given endpoint.
00730  * If the top flow is text (no internal structure), return 
00731  * the box of the flow
00732  * Else return the box of the subtable containing the point.
00733  * Because of spacing, the point might not be in any subtable.
00734  * In that case, return the top flow's box.
00735  * Note that box[0] must contain the edge point. Additional boxes
00736  * move out to the boundary.
00737  *
00738  * At present, unimplemented, since the label may be inside a
00739  * non-box node and we need to figure out what this means.
00740  */
00741 int html_path(node_t * n, port* p, int side, box * rv, int *k)
00742 {
00743 #ifdef UNIMPL
00744     point p;
00745     tbl_t *info;
00746     tbl_t *t;
00747     box b;
00748     int i;
00749 
00750     info = (tbl_t *) ND_shape_info(n);
00751     assert(info->tbls);
00752     info = info->tbls[0];       /* top-level flow */
00753     assert(IS_FLOW(info));
00754 
00755     b = info->box;
00756     if (info->tbl) {
00757         info = info->tbl;
00758         if (pt == 1)
00759             p = ED_tail_port(e).p;
00760         else
00761             p = ED_head_port(e).p;
00762         p = flip_pt(p, GD_rankdir(n->graph));   /* move p to node's coordinate system */
00763         for (i = 0; (t = info->tbls[i]) != 0; i++)
00764             if (INSIDE(p, t->box)) {
00765                 b = t->box;
00766                 break;
00767             }
00768     }
00769 
00770     /* move box into layout coordinate system */
00771     if (GD_flip(n->graph))
00772         b = flip_trans_box(b, ND_coord_i(n));
00773     else
00774         b = move_box(b, ND_coord_i(n));
00775 
00776     *k = 1;
00777     *rv = b;
00778     if (pt == 1)
00779         return BOTTOM;
00780     else
00781         return TOP;
00782 #endif
00783     return 0;
00784 }
00785 
00786 static int 
00787 size_html_txt(graph_t *g, htmltxt_t* ftxt, htmlenv_t* env)
00788 {
00789     double xsize = 0.0; /* width of text block */
00790     double ysize = 0.0; /* height of text block */
00791     double fsize;
00792     double lsize;    /* height of current line */
00793     double mxfsize = 0.0;  /* max. font size for the current line */
00794     double curbline = 0.0; /* dist. of current base line from top */
00795     pointf sz;
00796     int i, j, w, width;
00797     char *fname;
00798     textpara_t lp;
00799 
00800     for (i = 0; i < ftxt->nparas; i++) {
00801         width = w = 0;
00802         mxfsize = 0;
00803         for (j = 0; j < ftxt->paras[i].nitems; j++) {
00804             lp.str = strdup_and_subst_obj (ftxt->paras[i].items[j].str, env->obj);
00805             if (ftxt->paras[i].items[j].font) {
00806                 if (ftxt->paras[i].items[j].font->size > 0)
00807                     fsize = ftxt->paras[i].items[j].font->size;
00808                 else
00809                     fsize = env->finfo.size;
00810                 if (ftxt->paras[i].items[j].font->name)
00811                     fname = ftxt->paras[i].items[j].font->name;
00812                 else
00813                     fname = env->finfo.name;
00814             } else {
00815                 fsize = env->finfo.size;
00816                 fname = env->finfo.name;
00817             }
00818             sz = textsize(g, &lp, fname, fsize);
00819             free (ftxt->paras[i].items[j].str);
00820             ftxt->paras[i].items[j].str = lp.str;
00821             ftxt->paras[i].items[j].size = sz.x;
00822             ftxt->paras[i].items[j].yoffset_layout = lp.yoffset_layout;
00823             ftxt->paras[i].items[j].yoffset_centerline = lp.yoffset_centerline;
00824             ftxt->paras[i].items[j].postscript_alias = lp.postscript_alias;
00825             ftxt->paras[i].items[j].layout = lp.layout;
00826             ftxt->paras[i].items[j].free_layout = lp.free_layout;
00827             width += sz.x;
00828             mxfsize = MAX(fsize, mxfsize);
00829         }
00830         lsize = mxfsize * LINESPACING;
00831         ftxt->paras[i].size = (double) width;
00832             /* ysize - curbline is the distance from the previous
00833              * baseline to the bottom of the previous line.
00834              * Then, in the current line, we set the baseline to
00835              * be 5/6 of the max. font size. Thus, lfsize gives the
00836              * distance from the previous baseline to the new one.
00837              */
00838         ftxt->paras[i].lfsize = 5*mxfsize/6 + ysize - curbline;
00839         curbline += ftxt->paras[i].lfsize;
00840         xsize = MAX(width, xsize);
00841         ysize += lsize;
00842     }
00843     ftxt->box.UR.x = xsize;
00844     if (ftxt->nparas == 1)
00845         ftxt->box.UR.y = (int) (mxfsize);
00846     else
00847         ftxt->box.UR.y = (int) (ysize);
00848     return 0;
00849 }
00850 
00851 /* forward declarion for recursive usage */
00852 static int size_html_tbl(graph_t *g, htmltbl_t * tbl, htmlcell_t * parent,
00853                          htmlenv_t * env);
00854 
00855 /* size_html_img:
00856  */
00857 static int size_html_img(htmlimg_t * img, htmlenv_t * env)
00858 {
00859     box b;
00860     int rv;
00861 
00862     b.LL.x = b.LL.y = 0;
00863     b.UR = gvusershape_size(env->g, img->src);
00864     if ((b.UR.x == -1) && (b.UR.y == -1)) {
00865         rv = 1;
00866         b.UR.x = b.UR.y = 0;
00867         agerr(AGERR, "No or improper image file=\"%s\"\n", img->src);
00868     } else {
00869         rv = 0;
00870         GD_has_images(env->g) = TRUE;
00871     }
00872 
00873     img->box = b;
00874     return rv;
00875 }
00876 
00877 /* size_html_cell:
00878  */
00879 static int
00880 size_html_cell(graph_t *g, htmlcell_t * cp, htmltbl_t * parent, htmlenv_t * env)
00881 {
00882     int rv;
00883     point sz, child_sz;
00884     int margin;
00885 
00886     cp->parent = parent;
00887     if (!(cp->data.flags & PAD_SET)) {
00888         if (parent->data.flags & PAD_SET)
00889             cp->data.pad = parent->data.pad;
00890         else
00891             cp->data.pad = DEFAULT_CELLPADDING;
00892     }
00893     if (!(cp->data.flags & BORDER_SET)) {
00894         if (parent->cb >= 0)
00895             cp->data.border = parent->cb;
00896         else if (parent->data.flags & BORDER_SET)
00897             cp->data.border = parent->data.border;
00898         else
00899             cp->data.border = DEFAULT_BORDER;
00900     }
00901 
00902     if (cp->child.kind == HTML_TBL) {
00903         rv = size_html_tbl(g, cp->child.u.tbl, cp, env);
00904         child_sz = cp->child.u.tbl->data.box.UR;
00905     } else if (cp->child.kind == HTML_IMAGE) {
00906         rv = size_html_img(cp->child.u.img, env);
00907         child_sz = cp->child.u.img->box.UR;
00908     } else {
00909         rv = size_html_txt(g, cp->child.u.txt, env);
00910         child_sz = cp->child.u.txt->box.UR;
00911     }
00912 
00913     margin = 2 * (cp->data.pad + cp->data.border);
00914     sz.x = child_sz.x + margin;
00915     sz.y = child_sz.y + margin;
00916 
00917     if (cp->data.flags & FIXED_FLAG) {
00918         if (cp->data.width && cp->data.height) {
00919             if ((cp->data.width < sz.x) || (cp->data.height < sz.y)) {
00920                 agerr(AGWARN, "cell size too small for content\n");
00921                 rv = 1;
00922             }
00923             sz.x = sz.y = 0;
00924 
00925         } else {
00926             agerr(AGWARN,
00927                   "fixed cell size with unspecified width or height\n");
00928             rv = 1;
00929         }
00930     }
00931     cp->data.box.UR.x = MAX(sz.x, cp->data.width);
00932     cp->data.box.UR.y = MAX(sz.y, cp->data.height);
00933     return rv;
00934 }
00935 
00936 static int findCol(PointSet * ps, int row, int col, htmlcell_t * cellp)
00937 {
00938     int notFound = 1;
00939     int lastc;
00940     int i, j, c;
00941     int end = cellp->cspan - 1;
00942 
00943     while (notFound) {
00944         lastc = col + end;
00945         for (c = lastc; c >= col; c--) {
00946             if (isInPS(ps, c, row))
00947                 break;
00948         }
00949         if (c >= col)           /* conflict : try column after */
00950             col = c + 1;
00951         else
00952             notFound = 0;
00953     }
00954     for (j = col; j < col + cellp->cspan; j++) {
00955         for (i = row; i < row + cellp->rspan; i++) {
00956             addPS(ps, j, i);
00957         }
00958     }
00959     return col;
00960 }
00961 
00962 /* processTbl:
00963  * Convert parser representation of cells into final form.
00964  * Find column and row positions of cells.
00965  * Recursively size cells.
00966  * Return 1 if problem sizing a cell.
00967  */
00968 static int processTbl(graph_t *g, htmltbl_t * tbl, htmlenv_t * env)
00969 {
00970     pitem *rp;
00971     pitem *cp;
00972     Dt_t *cdict;
00973     int r, c, cnt;
00974     htmlcell_t *cellp;
00975     htmlcell_t **cells;
00976     Dt_t *rows = tbl->u.p.rows;
00977     int rv = 0;
00978     int n_rows = 0;
00979     int n_cols = 0;
00980     PointSet *ps = newPS();
00981 
00982     rp = (pitem *) dtflatten(rows);
00983     cnt = 0;
00984     while (rp) {
00985         cdict = rp->u.rp;
00986         cp = (pitem *) dtflatten(cdict);
00987         while (cp) {
00988             cellp = cp->u.cp;
00989             cnt++;
00990             cp = (pitem *) dtlink(cdict, (Dtlink_t *) cp);
00991         }
00992         rp = (pitem *) dtlink(rows, (Dtlink_t *) rp);
00993     }
00994 
00995     cells = tbl->u.n.cells = N_NEW(cnt + 1, htmlcell_t *);
00996     rp = (pitem *) dtflatten(rows);
00997     r = 0;
00998     while (rp) {
00999         cdict = rp->u.rp;
01000         cp = (pitem *) dtflatten(cdict);
01001         c = 0;
01002         while (cp) {
01003             cellp = cp->u.cp;
01004             *cells++ = cellp;
01005             rv |= size_html_cell(g, cellp, tbl, env);
01006             c = findCol(ps, r, c, cellp);
01007             cellp->row = r;
01008             cellp->col = c;
01009             c += cellp->cspan;
01010             n_cols = MAX(c, n_cols);
01011             n_rows = MAX(r + cellp->rspan, n_rows);
01012             cp = (pitem *) dtlink(cdict, (Dtlink_t *) cp);
01013         }
01014         rp = (pitem *) dtlink(rows, (Dtlink_t *) rp);
01015         r++;
01016     }
01017     tbl->rc = n_rows;
01018     tbl->cc = n_cols;
01019     dtclose(rows);
01020     freePS(ps);
01021     return rv;
01022 }
01023 
01024 /* Split size x over n pieces with spacing s.
01025  * We substract s*(n-1) from x, divide by n and 
01026  * take the ceiling.
01027  */
01028 #define SPLIT(x,n,s) (((x) - ((s)-1)*((n)-1)) / (n))
01029 
01030 /* sizeLinearArray:
01031  * Determine sizes of rows and columns. The size of a column is the
01032  * maximum width of any cell in it. Similarly for rows.
01033  * A cell spanning columns contributes proportionately to each column
01034  * it is in.
01035  */
01036 void sizeLinearArray(htmltbl_t * tbl)
01037 {
01038     htmlcell_t *cp;
01039     htmlcell_t **cells;
01040     int wd, ht, i, x, y;
01041 
01042     tbl->heights = N_NEW(tbl->rc + 1, int);
01043     tbl->widths = N_NEW(tbl->cc + 1, int);
01044 
01045     for (cells = tbl->u.n.cells; *cells; cells++) {
01046         cp = *cells;
01047         if (cp->rspan == 1)
01048             ht = cp->data.box.UR.y;
01049         else {
01050             ht = SPLIT(cp->data.box.UR.y, cp->rspan, tbl->data.space);
01051             ht = MAX(ht, 1);
01052         }
01053         if (cp->cspan == 1)
01054             wd = cp->data.box.UR.x;
01055         else {
01056             wd = SPLIT(cp->data.box.UR.x, cp->cspan, tbl->data.space);
01057             wd = MAX(wd, 1);
01058         }
01059         for (i = cp->row; i < cp->row + cp->rspan; i++) {
01060             y = tbl->heights[i];
01061             tbl->heights[i] = MAX(y, ht);
01062         }
01063         for (i = cp->col; i < cp->col + cp->cspan; i++) {
01064             x = tbl->widths[i];
01065             tbl->widths[i] = MAX(x, wd);
01066         }
01067     }
01068 }
01069 
01070 static char *nnames[] = {
01071     "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
01072     "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
01073 };
01074 
01075 /* nToName:
01076  * Convert int to its decimal string representation.
01077  */
01078 char *nToName(int c)
01079 {
01080     static char name[100];
01081 
01082     if (c < sizeof(nnames) / sizeof(char *))
01083         return nnames[c];
01084 
01085     sprintf(name, "%d", c);
01086     return name;
01087 }
01088 
01089 /* closeGraphs:
01090  * Clean up graphs made for setting column and row widths.
01091  */
01092 static void closeGraphs(graph_t * rowg, graph_t * colg)
01093 {
01094     node_t *n;
01095     for (n = GD_nlist(colg); n; n = ND_next(n)) {
01096         free_list(ND_in(n));
01097         free_list(ND_out(n));
01098     }
01099 
01100     agclose(rowg);
01101     agclose(colg);
01102 }
01103 
01104 static void checkChain(graph_t * g)
01105 {
01106     node_t *t;
01107     node_t *h;
01108     edge_t *e;
01109     t = GD_nlist(g);
01110     for (h = ND_next(t); h; h = ND_next(h)) {
01111         if (!agfindedge(g, t, h)) {
01112             e = agedge(g, t, h);
01113             ED_minlen(e) = 0;
01114             elist_append(e, ND_out(t));
01115             elist_append(e, ND_in(h));
01116         }
01117         t = h;
01118     }
01119 }
01120 
01121 /* makeGraphs:
01122  * Generate dags modeling the row and column constraints.
01123  * If the table has cc columns, we create the graph
01124  *  0 -> 1 -> 2 -> ... -> cc
01125  * and if a cell starts in column c with span cspan, with
01126  * width w, we add the edge c -> c+cspan [minlen = w].
01127  *
01128  * We might simplify the graph by removing multiedges,
01129  * using the max minlen, but will affect the balancing?
01130  */
01131 void makeGraphs(htmltbl_t * tbl, graph_t * rowg, graph_t * colg)
01132 {
01133     htmlcell_t *cp;
01134     htmlcell_t **cells;
01135     node_t *t;
01136     node_t *lastn;
01137     node_t *h;
01138     edge_t *e;
01139     int i;
01140     int* minc;
01141     int* minr;
01142 
01143     lastn = NULL;
01144     for (i = 0; i <= tbl->cc; i++) {
01145         t = agnode(colg, nToName(i));
01146         alloc_elist(tbl->rc, ND_in(t));
01147         alloc_elist(tbl->rc, ND_out(t));
01148         if (lastn) {
01149             ND_next(lastn) = t;
01150             lastn = t;
01151         } else {
01152             lastn = GD_nlist(colg) = t;
01153         }
01154     }
01155     lastn = NULL;
01156     for (i = 0; i <= tbl->rc; i++) {
01157         t = agnode(rowg, nToName(i));
01158         alloc_elist(tbl->cc, ND_in(t));
01159         alloc_elist(tbl->cc, ND_out(t));
01160         if (lastn) {
01161             ND_next(lastn) = t;
01162             lastn = t;
01163         } else {
01164             lastn = GD_nlist(rowg) = t;
01165         }
01166     }
01167     minr = N_NEW(tbl->rc, int);
01168     minc = N_NEW(tbl->cc, int);
01169     for (cells = tbl->u.n.cells; *cells; cells++) {
01170         int x, y, c, r;
01171         cp = *cells;
01172         x = (cp->data.box.UR.x + (cp->cspan-1))/cp->cspan;
01173         for (c = 0; c < cp->cspan; c++)
01174           minc[cp->col + c] = MAX(minc[cp->col + c],x);
01175         y = (cp->data.box.UR.y + (cp->rspan-1))/cp->rspan;
01176         for (r = 0; r < cp->rspan; r++)
01177           minr[cp->row + r] = MAX(minr[cp->row + r],y);
01178     }
01179     for (cells = tbl->u.n.cells; *cells; cells++) {
01180         int x, y, c, r;
01181         cp = *cells;
01182         t = agfindnode(colg, nToName(cp->col));
01183         h = agfindnode(colg, nToName(cp->col + cp->cspan));
01184         e = agedge(colg, t, h);
01185         x = 0;
01186         for (c = 0; c < cp->cspan; c++)
01187             x += minc[cp->col + c];
01188         ED_minlen(e) = x;
01189         /* ED_minlen(e) = cp->data.box.UR.x; */
01190 #ifdef DEBUG
01191         fprintf(stderr, "col edge %s -> %s %d\n", t->name, h->name,
01192                 ED_minlen(e));
01193 #endif
01194         elist_append(e, ND_out(t));
01195         elist_append(e, ND_in(h));
01196 
01197         t = agfindnode(rowg, nToName(cp->row));
01198         h = agfindnode(rowg, nToName(cp->row + cp->rspan));
01199         e = agedge(rowg, t, h);
01200         y = 0;
01201         for (r = 0; r < cp->rspan; r++)
01202             y += minr[cp->row + r];
01203         ED_minlen(e) = y;
01204         /* ED_minlen(e) = cp->data.box.UR.y; */
01205 #ifdef DEBUG
01206         fprintf(stderr, "row edge %s -> %s %d\n", t->name, h->name,
01207                 ED_minlen(e));
01208 #endif
01209         elist_append(e, ND_out(t));
01210         elist_append(e, ND_in(h));
01211     }
01212 
01213     /* Make sure that 0 <= 1 <= 2 ...k. This implies graph connected. */
01214     checkChain(colg);
01215     checkChain(rowg);
01216 
01217     free (minc);
01218     free (minr);
01219 }
01220 
01221 /* setSizes:
01222  * Use rankings to determine cell dimensions. The rank values
01223  * give the coordinate, so to get the width/height, we have
01224  * to subtract the previous value.
01225  */
01226 void setSizes(htmltbl_t * tbl, graph_t * rowg, graph_t * colg)
01227 {
01228     int i;
01229     node_t *n;
01230     int prev;
01231 
01232     prev = 0;
01233     n = GD_nlist(rowg);
01234     for (i = 0, n = ND_next(n); n; i++, n = ND_next(n)) {
01235         tbl->heights[i] = ND_rank(n) - prev;
01236         prev = ND_rank(n);
01237     }
01238     prev = 0;
01239     n = GD_nlist(colg);
01240     for (i = 0, n = ND_next(n); n; i++, n = ND_next(n)) {
01241         tbl->widths[i] = ND_rank(n) - prev;
01242         prev = ND_rank(n);
01243     }
01244 
01245 }
01246 
01247 /* sizeArray:
01248  * Set column and row sizes. Optimize for minimum width and
01249  * height. Where there is slack, try to distribute evenly.
01250  * We do this by encoding cells as edges with min length is
01251  * a dag on a chain. We then run network simplex, using
01252  * LR_balance.
01253  */
01254 void sizeArray(htmltbl_t * tbl)
01255 {
01256     graph_t *rowg;
01257     graph_t *colg;
01258 
01259     /* Do the 1D cases by hand */
01260     if ((tbl->rc == 1) || (tbl->cc == 1)) {
01261         sizeLinearArray(tbl);
01262         return;
01263     }
01264 
01265     tbl->heights = N_NEW(tbl->rc + 1, int);
01266     tbl->widths = N_NEW(tbl->cc + 1, int);
01267 
01268     rowg = agopen("rowg", AGDIGRAPH);
01269     colg = agopen("colg", AGDIGRAPH);
01270     makeGraphs(tbl, rowg, colg);
01271     rank(rowg, 2, INT_MAX);
01272     rank(colg, 2, INT_MAX);
01273     setSizes(tbl, rowg, colg);
01274     closeGraphs(rowg, colg);
01275 }
01276 
01277 static void pos_html_tbl(htmltbl_t *, box, int);  /* forward declaration */
01278 
01279 /* pos_html_img:
01280  * Place image in cell
01281  * storing allowed space handed by parent cell.
01282  * How this space is used is handled in emit_html_img.
01283  */
01284 static void pos_html_img(htmlimg_t * cp, box pos)
01285 {
01286     cp->box = pos;
01287 }
01288 
01289 /* pos_html_txt:
01290  * Set default alignment.
01291  */
01292 static void
01293 pos_html_txt(htmltxt_t* ftxt, char c)
01294 {
01295     int i;
01296 
01297     for (i = 0; i < ftxt->nparas; i++) {
01298         if (ftxt->paras[i].just == UNSET_ALIGN)  /* unset */
01299             ftxt->paras[i].just = c;
01300     }
01301 }
01302 
01303 /* pos_html_cell:
01304  */
01305 static void pos_html_cell(htmlcell_t * cp, box pos, int sides)
01306 {
01307     int delx, dely;
01308     point oldsz;
01309     box cbox;
01310 
01311     if (!cp->data.pencolor)
01312         cp->data.pencolor = cp->parent->data.pencolor;
01313 
01314     /* If fixed, align cell */
01315     if (cp->data.flags & FIXED_FLAG) {
01316         oldsz = cp->data.box.UR;
01317         delx = (pos.UR.x - pos.LL.x) - oldsz.x;
01318         if (delx > 0) {
01319             switch (cp->data.flags & HALIGN_MASK) {
01320             case HALIGN_LEFT:
01321                 pos.UR.x = pos.LL.x + oldsz.x;
01322                 break;
01323             case HALIGN_RIGHT:
01324                 pos.UR.x += delx;
01325                 pos.LL.x += delx;
01326                 break;
01327             default:
01328                 pos.LL.x += delx / 2;
01329                 pos.UR.x -= delx / 2;
01330                 break;
01331             }
01332         }
01333         dely = (pos.UR.y - pos.LL.y) - oldsz.y;
01334         if (dely > 0) {
01335             switch (cp->data.flags & VALIGN_MASK) {
01336             case VALIGN_BOTTOM:
01337                 pos.UR.y = pos.LL.y + oldsz.y;
01338                 break;
01339             case VALIGN_TOP:
01340                 pos.UR.y += dely;
01341                 pos.LL.y += dely;
01342                 break;
01343             default:
01344                 pos.LL.y += dely / 2;
01345                 pos.UR.y -= dely / 2;
01346                 break;
01347             }
01348         }
01349     }
01350     cp->data.box = pos;
01351     cp->data.sides = sides;
01352 
01353     /* set up child's position */
01354     cbox.LL.x = pos.LL.x + cp->data.border + cp->data.pad;
01355     cbox.LL.y = pos.LL.y + cp->data.border + cp->data.pad;
01356     cbox.UR.x = pos.UR.x - cp->data.border - cp->data.pad;
01357     cbox.UR.y = pos.UR.y - cp->data.border - cp->data.pad;
01358 
01359     if (cp->child.kind == HTML_TBL) {
01360         pos_html_tbl(cp->child.u.tbl, cbox, sides);
01361     } else if (cp->child.kind == HTML_IMAGE) {
01362         pos_html_img(cp->child.u.img, cbox);
01363     } else {
01364         char dfltalign;
01365         int af;
01366 
01367         oldsz = cp->child.u.txt->box.UR;
01368         delx = (cbox.UR.x - cbox.LL.x) - oldsz.x;
01369         /* If the cell is larger than the text block and alignment is 
01370          * done at textblock level, the text box is shrunk accordingly. 
01371          */
01372         if ((delx > 0)&&((af=(cp->data.flags & HALIGN_MASK)) != HALIGN_TEXT)) {
01373             switch (af) {
01374             case HALIGN_LEFT:
01375                 cbox.UR.x -= delx;
01376                 break;
01377             case HALIGN_RIGHT:
01378                 cbox.LL.x += delx;
01379                 break;
01380             default:
01381                 cbox.LL.x += delx / 2;
01382                 cbox.UR.x -= delx / 2;
01383                 break;
01384             }
01385         }
01386 
01387         dely = (cbox.UR.y - cbox.LL.y) - oldsz.y;
01388         if (dely > 0) {
01389             switch (cp->data.flags & VALIGN_MASK) {
01390             case VALIGN_BOTTOM:
01391                 cbox.UR.y -= dely;
01392                 break;
01393             case VALIGN_TOP:
01394                 cbox.LL.y += dely;
01395                 break;
01396             default:
01397                 cbox.LL.y += dely / 2;
01398                 cbox.UR.y -= dely / 2;
01399                 break;
01400             }
01401         }
01402         cp->child.u.txt->box = cbox;
01403 
01404         /* Set default text alignment
01405          */
01406         switch (cp->data.flags & BALIGN_MASK) {
01407         case BALIGN_LEFT:
01408             dfltalign = 'l';
01409             break;
01410         case BALIGN_RIGHT:
01411             dfltalign = 'r';
01412             break;
01413         default:
01414             dfltalign = 'n';
01415             break;
01416         }
01417         pos_html_txt (cp->child.u.txt, dfltalign);
01418     }
01419 }
01420 
01421 /* pos_html_tbl:
01422  * Position table given its box, then calculate
01423  * the position of each cell. In addition, set the sides
01424  * attribute indicating which external sides of the node
01425  * are accessible to the table.
01426  */
01427 static void pos_html_tbl(htmltbl_t * tbl, box pos, int sides)
01428 {
01429     int x, y, delx, dely;
01430     int i, plus, extra, oldsz;
01431     htmlcell_t **cells = tbl->u.n.cells;
01432     htmlcell_t *cp;
01433     box cbox;
01434 
01435     if (tbl->u.n.parent && !tbl->data.pencolor)
01436         tbl->data.pencolor = tbl->u.n.parent->data.pencolor;
01437 
01438     oldsz = tbl->data.box.UR.x;
01439     delx = (pos.UR.x - pos.LL.x) - oldsz;
01440     assert(delx >= 0);
01441     oldsz = tbl->data.box.UR.y;
01442     dely = (pos.UR.y - pos.LL.y) - oldsz;
01443     assert(dely >= 0);
01444 
01445     /* If fixed, align box */
01446     if (tbl->data.flags & FIXED_FLAG) {
01447         if (delx > 0) {
01448             switch (tbl->data.flags & HALIGN_MASK) {
01449             case HALIGN_LEFT:
01450                 pos.UR.x = pos.LL.x + oldsz;
01451                 break;
01452             case HALIGN_RIGHT:
01453                 pos.UR.x += delx;
01454                 pos.LL.x += delx;
01455                 break;
01456             default:
01457                 pos.LL.x += delx / 2;
01458                 pos.UR.x -= delx / 2;
01459                 break;
01460             }
01461             delx = 0;
01462         }
01463         if (dely > 0) {
01464             switch (tbl->data.flags & VALIGN_MASK) {
01465             case VALIGN_BOTTOM:
01466                 pos.UR.y = pos.LL.y + oldsz;
01467                 break;
01468             case VALIGN_TOP:
01469                 pos.UR.y += dely;
01470                 pos.LL.y += dely;
01471                 break;
01472             default:
01473                 pos.LL.y += dely / 2;
01474                 pos.UR.y -= dely / 2;
01475                 break;
01476             }
01477             dely = 0;
01478         }
01479     }
01480 
01481     /* change sizes to start positions and distribute extra space */
01482     x = pos.LL.x + tbl->data.border + tbl->data.space;
01483     extra = delx / (tbl->cc);
01484     plus = delx - extra * (tbl->cc);
01485     for (i = 0; i <= tbl->cc; i++) {
01486         delx = tbl->widths[i] + extra + (i < plus ? 1 : 0);
01487         tbl->widths[i] = x;
01488         x += delx + tbl->data.space;
01489     }
01490     y = pos.UR.y - tbl->data.border - tbl->data.space;
01491     extra = dely / (tbl->rc);
01492     plus = dely - extra * (tbl->rc);
01493     for (i = 0; i <= tbl->rc; i++) {
01494         dely = tbl->heights[i] + extra + (i < plus ? 1 : 0);
01495         tbl->heights[i] = y;
01496         y -= dely + tbl->data.space;
01497     }
01498 
01499     while ((cp = *cells++)) {
01500         int mask = 0;
01501         if (sides) {
01502             if (cp->col == 0) mask |= LEFT;
01503             if (cp->row == 0) mask |= TOP;
01504             if (cp->col + cp->cspan == tbl->cc) mask |= RIGHT;
01505             if (cp->row + cp->rspan == tbl->rc) mask |= BOTTOM;
01506         }
01507         cbox.LL.x = tbl->widths[cp->col];
01508         cbox.UR.x = tbl->widths[cp->col + cp->cspan] - tbl->data.space;
01509         cbox.UR.y = tbl->heights[cp->row];
01510         cbox.LL.y = tbl->heights[cp->row + cp->rspan] + tbl->data.space;
01511         pos_html_cell(cp, cbox, sides & mask);
01512     }
01513 
01514     tbl->data.sides = sides;
01515     tbl->data.box = pos;
01516 }
01517 
01518 /* size_html_tbl:
01519  * Determine the size of a table by first determining the
01520  * size of each cell.
01521  */
01522 static int
01523 size_html_tbl(graph_t *g, htmltbl_t * tbl, htmlcell_t * parent, htmlenv_t * env)
01524 {
01525     int i, wd, ht;
01526     int rv = 0;
01527     htmlfont_t savef;
01528 
01529     if (tbl->font)
01530         pushFontInfo(env, tbl->font, &savef);
01531     tbl->u.n.parent = parent;
01532     rv = processTbl(g, tbl, env);
01533 
01534     /* Set up border and spacing */
01535     if (!(tbl->data.flags & SPACE_SET)) {
01536         tbl->data.space = DEFAULT_CELLSPACING;
01537     }
01538     if (!(tbl->data.flags & BORDER_SET)) {
01539         tbl->data.border = DEFAULT_BORDER;
01540     }
01541 
01542     sizeArray(tbl);
01543 
01544     wd = (tbl->cc + 1) * tbl->data.space + 2 * tbl->data.border;
01545     ht = (tbl->rc + 1) * tbl->data.space + 2 * tbl->data.border;
01546     for (i = 0; i < tbl->cc; i++)
01547         wd += tbl->widths[i];
01548     for (i = 0; i < tbl->rc; i++)
01549         ht += tbl->heights[i];
01550 
01551     if (tbl->data.flags & FIXED_FLAG) {
01552         if (tbl->data.width && tbl->data.height) {
01553             if ((tbl->data.width < wd) || (tbl->data.height < ht)) {
01554                 agerr(AGWARN, "table size too small for content\n");
01555                 rv = 1;
01556             }
01557             wd = ht = 0;
01558         } else {
01559             agerr(AGWARN,
01560                   "fixed table size with unspecified width or height\n");
01561             rv = 1;
01562         }
01563     }
01564     tbl->data.box.UR.x = MAX(wd, tbl->data.width);
01565     tbl->data.box.UR.y = MAX(ht, tbl->data.height);
01566 
01567     if (tbl->font)
01568         popFontInfo(env, &savef);
01569     return rv;
01570 }
01571 
01572 static char *nameOf(void *obj, agxbuf * xb)
01573 {
01574     Agedge_t *ep;
01575     switch (agobjkind(obj)) {
01576     case AGGRAPH:
01577         agxbput(xb, ((Agraph_t *) obj)->name);
01578         break;
01579     case AGNODE:
01580         agxbput(xb, ((Agnode_t *) obj)->name);
01581         break;
01582     case AGEDGE:
01583         ep = (Agedge_t *) obj;
01584         agxbput(xb, ep->tail->name);
01585         agxbput(xb, ep->head->name);
01586         if (AG_IS_DIRECTED(ep->tail->graph))
01587             agxbput(xb, "->");
01588         else
01589             agxbput(xb, "--");
01590         break;
01591     }
01592     return agxbuse(xb);
01593 }
01594 
01595 #ifdef DEBUG
01596 void indent(int i)
01597 {
01598     while (i--)
01599         fprintf(stderr, "  ");
01600 }
01601 
01602 void printBox(box b)
01603 {
01604     fprintf(stderr, "(%d,%d)(%d,%d)", b.LL.x, b.LL.y, b.UR.x, b.UR.y);
01605 }
01606 
01607 void printImage(htmlimg_t *ip, int ind)
01608 {
01609     indent(ind);
01610     fprintf(stderr, "img: %s\n", ip->src);
01611 }
01612 
01613 void printTxt(htmltxt_t * txt, int ind)
01614 {
01615     int i, j;
01616 
01617     indent(ind);
01618     fprintf (stderr, "txt paras = %d \n", txt->nparas);
01619     for (i = 0; i < txt->nparas; i++) {
01620         indent(ind+1);
01621         fprintf (stderr, "[%d] %d items\n", i, txt->paras[i].nitems);
01622         for (j = 0; j < txt->paras[i].nitems; j++) {
01623             indent(ind+2);
01624             fprintf (stderr, "[%d] (%f) \"%s\" ",
01625                    j, txt->paras[i].items[j].size,
01626                    txt->paras[i].items[j].str);
01627             if (txt->paras[i].items[j].font)
01628               fprintf (stderr, "font %s color %s size %f\n",
01629                    txt->paras[i].items[j].font->name,
01630                    txt->paras[i].items[j].font->color,
01631                    txt->paras[i].items[j].font->size);
01632             else fprintf (stderr, "\n");
01633         }
01634     }
01635 }
01636 
01637 void printData(htmldata_t * dp)
01638 {
01639     unsigned char flags = dp->flags;
01640     char c;
01641 
01642     fprintf(stderr, "s%d(%d) ", dp->space, (flags & SPACE_SET ? 1 : 0));
01643     fprintf(stderr, "b%d(%d) ", dp->border, (flags & BORDER_SET ? 1 : 0));
01644     fprintf(stderr, "p%d(%d) ", dp->pad, (flags & PAD_SET ? 1 : 0));
01645     switch (flags & HALIGN_MASK) {
01646     case HALIGN_RIGHT:
01647         c = 'r';
01648         break;
01649     case HALIGN_LEFT:
01650         c = 'l';
01651         break;
01652     default:
01653         c = 'n';
01654         break;
01655     }
01656     fprintf(stderr, "%c", c);
01657     switch (flags & VALIGN_MASK) {
01658     case VALIGN_TOP:
01659         c = 't';
01660         break;
01661     case VALIGN_BOTTOM:
01662         c = 'b';
01663         break;
01664     default:
01665         c = 'c';
01666         break;
01667     }
01668     fprintf(stderr, "%c ", c);
01669     printBox(dp->box);
01670 }
01671 
01672 void printTbl(htmltbl_t * tbl, int ind)
01673 {
01674     htmlcell_t **cells = tbl->u.n.cells;
01675     indent(ind);
01676     fprintf(stderr, "tbl %d %d ", tbl->cc, tbl->rc);
01677     printData(&tbl->data);
01678     fputs("\n", stderr);
01679     while (*cells)
01680         printCell(*cells++, ind + 1);
01681 }
01682 
01683 static void printCell(htmlcell_t * cp, int ind)
01684 {
01685     indent(ind);
01686     fprintf(stderr, "cell %d %d %d %d ", cp->cspan, cp->rspan, cp->col,
01687             cp->row);
01688     printData(&cp->data);
01689     fputs("\n", stderr);
01690     switch (cp->child.kind) {
01691     case HTML_TBL:
01692         printTbl(cp->child.u.tbl, ind + 1);
01693         break;
01694     case HTML_TEXT:
01695         printTxt(cp->child.u.txt, ind + 1);
01696         break;
01697     case HTML_IMAGE:
01698         printImage(cp->child.u.img, ind + 1);
01699         break;
01700     default:
01701         break;
01702     }
01703 }
01704 
01705 void printLbl(htmllabel_t * lbl)
01706 {
01707     if (lbl->kind == HTML_TBL)
01708         printTbl(lbl->u.tbl, 0);
01709     else
01710         printTxt(lbl->u.txt, 0);
01711 }
01712 #endif                          /* DEBUG */
01713 
01714 static char *getPenColor(void *obj)
01715 {
01716     char *str;
01717 
01718     if (((str = agget(obj, "pencolor")) != 0) && str[0])
01719         return str;
01720     else if (((str = agget(obj, "color")) != 0) && str[0])
01721         return str;
01722     else
01723         return NULL;
01724 }
01725 
01726 /* make_html_label:
01727  * Return non-zero if problem parsing HTML. In this case, use object name.
01728  */
01729 int make_html_label(graph_t *g, textlabel_t * lp, void *obj)
01730 {
01731     int rv;
01732     int wd2, ht2;
01733     box box;
01734     htmllabel_t *lbl;
01735     htmlenv_t env;
01736 
01737     env.obj = obj;
01738     switch (agobjkind(obj)) {
01739     case AGGRAPH:
01740         env.g = ((Agraph_t *) obj)->root;
01741         break;
01742     case AGNODE:
01743         env.g = ((Agnode_t *) obj)->graph;
01744         break;
01745     case AGEDGE:
01746         env.g = ((Agedge_t *) obj)->head->graph;
01747         break;
01748     }
01749     env.finfo.size = lp->fontsize;
01750     env.finfo.name = lp->fontname;
01751     env.finfo.color = lp->fontcolor;
01752     lbl = parseHTML(lp->text, &rv, GD_charset(env.g));
01753     if (!lbl) {
01754         /* Parse of label failed; revert to simple text label */
01755         agxbuf xb;
01756         unsigned char buf[SMALLBUF];
01757         agxbinit(&xb, SMALLBUF, buf);
01758         lp->html = FALSE;
01759         lp->text = strdup(nameOf(obj, &xb));
01760         size_label(env.g, lp);
01761         agxbfree(&xb);
01762         return rv;
01763     }
01764 
01765     if (lbl->kind == HTML_TBL) {
01766         lbl->u.tbl->data.pencolor = getPenColor(obj);
01767         rv |= size_html_tbl(g, lbl->u.tbl, NULL, &env);
01768         wd2 = (lbl->u.tbl->data.box.UR.x + 1) / 2;
01769         ht2 = (lbl->u.tbl->data.box.UR.y + 1) / 2;
01770         box = boxof(-wd2, -ht2, wd2, ht2);
01771         pos_html_tbl(lbl->u.tbl, box, BOTTOM | RIGHT | TOP | LEFT);
01772         lp->dimen.x = box.UR.x - box.LL.x;
01773         lp->dimen.y = box.UR.y - box.LL.y;
01774     } else {
01775         rv |= size_html_txt(g, lbl->u.txt, &env);
01776         wd2 = (lbl->u.txt->box.UR.x + 1) / 2;
01777         ht2 = (lbl->u.txt->box.UR.y + 1) / 2;
01778         box = boxof(-wd2, -ht2, wd2, ht2);
01779         lbl->u.txt->box = box;
01780         lp->dimen.x = box.UR.x - box.LL.x;
01781         lp->dimen.y = box.UR.y - box.LL.y;
01782     }
01783 
01784     lp->u.html = lbl;
01785 
01786     /* If the label is a table, replace label text because this may
01787      * be used for the title and alt fields in image maps.
01788      */
01789     if (lbl->kind == HTML_TBL) {
01790         free (lp->text);
01791         lp->text = strdup ("<TABLE>");
01792     }
01793 
01794     return rv;
01795 }
01796 

Generated on Mon Mar 31 19:03:24 2008 for Graphviz by  doxygen 1.5.1