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