/* $Id: htmltable.c,v 1.91 2011/04/06 17:50:52 erg Exp $ $Revision: 1.91 $ */ /* vim:set shiftwidth=4 ts=8: */ /************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: See CVS logs. Details at http://www.graphviz.org/ *************************************************************************/ /* Implementation of HTML-like tables. * * The (now purged) CodeGen graphics model, especially with integral coodinates, is * not adequate to handle this as we would like. In particular, it is * difficult to handle notions of adjacency and correct rounding to pixels. * For example, if 2 adjacent boxes bb1.UR.x == bb2.LL.x, the rectangles * may be drawn overlapping. However, if we use bb1.UR.x+1 == bb2.LL.x * there may or may not be a gap between them, even in the same device * depending on their positions. When CELLSPACING > 1, this isn't as much * of a problem. * * We allow negative spacing as a hack to allow overlapping cell boundaries. * For the reasons discussed above, this is difficult to get correct. * This is an important enough case we should extend the table model to * support it correctly. This could be done by allowing a table attribute, * e.g., CELLGRID=n, which sets CELLBORDER=0 and has the border drawing * handled correctly by the table. */ #include <assert.h> #include "render.h" #include "htmltable.h" #include "agxbuf.h" #include "pointset.h" #define DEFAULT_BORDER 1 #define DEFAULT_CELLPADDING 2 #define DEFAULT_CELLSPACING 2 typedef struct { pointf pos; htmlfont_t finfo; void *obj; graph_t *g; char* imgscale; char* objid; boolean objid_set; } htmlenv_t; typedef struct { char *url; char *tooltip; char *target; char *id; boolean explicit_tooltip; point LL; point UR; } htmlmap_data_t; #ifdef DEBUG static void printCell(htmlcell_t * cp, int ind); #endif /* pushFontInfo: * Replace current font attributes in env with ones from fp, * storing old attributes in savp. We only deal with attributes * set in env. The attributes are restored via popFontInfo. */ static void pushFontInfo(htmlenv_t * env, htmlfont_t * fp, htmlfont_t * savp) { if (env->finfo.name) { if (fp->name) { savp->name = env->finfo.name; env->finfo.name = fp->name; } else savp->name = NULL; } if (env->finfo.color) { if (fp->color) { savp->color = env->finfo.color; env->finfo.color = fp->color; } else savp->color = NULL; } if (env->finfo.size >= 0) { if (fp->size >= 0) { savp->size = env->finfo.size; env->finfo.size = fp->size; } else savp->size = -1.0; } } /* popFontInfo: * Restore saved font attributes. * Copy only set values. */ static void popFontInfo(htmlenv_t * env, htmlfont_t * savp) { if (savp->name) env->finfo.name = savp->name; if (savp->color) env->finfo.color = savp->color; if (savp->size >= 0.0) env->finfo.size = savp->size; } static void emit_htextparas(GVJ_t* job, int nparas, htextpara_t* paras, pointf p, double halfwidth_x, htmlfont_t finfo, boxf b) { int i,j; double center_x, left_x, right_x, fsize_; char *fname_ , *fcolor_; textpara_t tl; pointf p_ = {0.0, 0.0}; textpara_t* ti; center_x = p.x; left_x = center_x - halfwidth_x; right_x = center_x + halfwidth_x; /* Initial p is in center of text block; set initial baseline * to top of text block. */ p_.y = p.y + (b.UR.y-b.LL.y)/2.0; gvrender_begin_label(job, LABEL_HTML); for(i=0; i<nparas; i++) { /* set p.x to leftmost point where the line of text begins */ switch (paras[i].just) { case 'l': p.x = left_x; break; case 'r': p.x = right_x - paras[i].size; break; default: case 'n': p.x = center_x - paras[i].size/2.0; break; } p_.y -= paras[i].lfsize; /* move to current base line */ ti = paras[i].items; for(j=0; j<paras[i].nitems; j++) { if (ti->font && (ti->font->size > 0)) fsize_ = ti->font->size; else fsize_ = finfo.size; if (ti->font && ti->font->name) fname_ = ti->font->name; else fname_ = finfo.name; if (ti->font && ti->font->color) fcolor_ = ti->font->color; else fcolor_ = finfo.color; gvrender_set_pencolor(job, fcolor_); tl.str = ti->str; tl.fontname = fname_; tl.fontsize = fsize_; tl.yoffset_layout = ti->yoffset_layout; /* tl.yoffset_centerline = ti->yoffset_centerline; */ tl.yoffset_centerline = 1; tl.postscript_alias = ti->postscript_alias; tl.layout = ti->layout; tl.width = ti->size; tl.height = paras[i].lfsize; tl.just = 'l'; p_.x = p.x; gvrender_textpara(job, p_, &tl); p.x += ti->size; ti++; } } gvrender_end_label(job); } static void emit_html_txt(GVJ_t* job, htmltxt_t* tp, htmlenv_t* env) { double halfwidth_x; pointf p; /* make sure that there is something to do */ if (tp->nparas < 1) return; halfwidth_x = ((double) (tp->box.UR.x - tp->box.LL.x)) / 2.0; p.x = env->pos.x + ((double) (tp->box.UR.x + tp->box.LL.x)) / 2.0; p.y = env->pos.y + ((double) (tp->box.UR.y + tp->box.LL.y)) / 2.0; emit_htextparas(job, tp->nparas, tp->paras, p, halfwidth_x, env->finfo, tp->box); } static void doSide(GVJ_t * job, pointf p, double wd, double ht) { boxf BF; BF.LL = p; BF.UR.x = p.x + wd; BF.UR.y = p.y + ht; gvrender_box(job, BF, 1); } /* doBorder: * Draw rectangle of width border inside rectangle given * by box. If border is 1, we call use a single call to gvrender_polygon. * (We have set linewidth to 1 below.) Otherwise, we use four separate * filled rectangles. We could use a richer graphics model, as things * can go wrong when cell spacing and borders are small. * We decrement the border value by 1, as typically a filled rectangle * from x to x+border will all pixels from x to x+border, and thus have * width border+1. */ static void doBorder(GVJ_t * job, char *color, int border, boxf BF) { pointf pt; double wd, ht; if (!color) color = DEFAULT_COLOR; gvrender_set_fillcolor(job, color); gvrender_set_pencolor(job, color); if (border == 1) { gvrender_box(job, BF, 0); } else { border--; ht = BF.UR.y - BF.LL.y; wd = BF.UR.x - BF.LL.x; doSide(job, BF.LL, border, ht); pt.x = BF.LL.x; pt.y = BF.UR.y; doSide(job, pt, wd, -border); doSide(job, BF.UR, -border, -ht); pt.x = BF.UR.x; pt.y = BF.LL.y; doSide(job, pt, -wd, border); } } static void doFill(GVJ_t * job, char *color, boxf BF) { gvrender_set_fillcolor(job, color); gvrender_set_pencolor(job, color); gvrender_box(job, BF, 1); } /* initAnchor: * Save current map values * Initialize fields in job->obj pertaining to anchors. * In particular, this also sets the output rectangle. * If there is something to do, close current anchor if * necessary, start the anchor and returns 1. * Otherwise, it returns 0. * * FIX: Should we provide a tooltip if none is set, as is done * for nodes, edges, etc. ? */ static int initAnchor (GVJ_t* job, htmlenv_t* env, htmldata_t* data, boxf b, htmlmap_data_t* save, int closePrev) { obj_state_t *obj = job->obj; int changed; char* id; static int anchorId; int internalId = 0; agxbuf xb; char intbuf[30]; /* hold 64-bit decimal integer */ unsigned char buf[SMALLBUF]; save->url = obj->url; save->tooltip = obj->tooltip; save->target = obj->target; save->id = obj->id; save->explicit_tooltip = obj->explicit_tooltip; id = data->id; if (!id || !*id) { /* no external id, so use the internal one */ agxbinit(&xb, SMALLBUF, buf); if (!env->objid) { env->objid = strdup (getObjId (job, obj->u.n, &xb)); env->objid_set = 1; } agxbput (&xb, env->objid); sprintf (intbuf, "_%d", anchorId++); agxbput (&xb, intbuf); id = agxbuse (&xb); internalId = 1; } changed = initMapData (job, NULL, data->href, data->title, data->target, id, obj->u.g); if (internalId) agxbfree (&xb); if (changed) { if (closePrev && (save->url || save->explicit_tooltip)) gvrender_end_anchor(job); if (obj->url || obj->explicit_tooltip) { emit_map_rect(job, b); gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id); } } return changed; } #define RESET(fld) \ if(obj->fld != save->fld) {free(obj->fld); obj->fld = save->fld;} /* endAnchor: * Pop context pushed by initAnchor. * This is done by ending current anchor, restoring old values and * freeing new, and reopening previous anchor if necessary. * * NB: We don't save or restore geometric map info. This is because * this preservation of map context is only necessary for SVG-like * systems where graphical items are wrapped in an anchor, and we map * top-down. For ordinary map anchors, this is all done bottom-up, so * the geometric map info at the higher level hasn't been emitted yet. */ static void endAnchor (GVJ_t* job, htmlmap_data_t* save, int openPrev) { obj_state_t *obj = job->obj; if (obj->url || obj->explicit_tooltip) gvrender_end_anchor(job); RESET(url); RESET(tooltip); RESET(target); RESET(id); obj->explicit_tooltip = save->explicit_tooltip; if (openPrev && (obj->url || obj->explicit_tooltip)) gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id); } /* forward declaration */ static void emit_html_cell(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env); static void emit_html_tbl(GVJ_t * job, htmltbl_t * tbl, htmlenv_t * env) { boxf pts = tbl->data.box; pointf pos = env->pos; htmlcell_t **cells = tbl->u.n.cells; static htmlfont_t savef; htmlmap_data_t saved; int anchor; /* if true, we need to undo anchor settings. */ int doAnchor = (tbl->data.href || tbl->data.target); if (tbl->font) pushFontInfo(env, tbl->font, &savef); pts.LL.x += pos.x; pts.UR.x += pos.x; pts.LL.y += pos.y; pts.UR.y += pos.y; if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST)) anchor = initAnchor(job, env, &tbl->data, pts, &saved, 1); else anchor = 0; if (tbl->style & ROUNDED) { pointf AF[4]; char* color = (tbl->data.pencolor ? tbl->data.pencolor : DEFAULT_COLOR); AF[0] = pts.LL; AF[2] = pts.UR; AF[1].x = AF[2].x; AF[1].y = AF[0].y; AF[3].x = AF[0].x; AF[3].y = AF[2].y; round_corners (job, tbl->data.bgcolor, color, AF, 4, tbl->style, (tbl->data.bgcolor != NULL)); } else { if (tbl->data.bgcolor) doFill(job, tbl->data.bgcolor, pts); if (tbl->data.border) doBorder(job, tbl->data.pencolor, tbl->data.border, pts); } while (*cells) { emit_html_cell(job, *cells, env); cells++; } if (anchor) endAnchor (job, &saved, 1); if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) { if (initAnchor(job, env, &tbl->data, pts, &saved, 0)) endAnchor (job, &saved, 0); } if (tbl->font) popFontInfo(env, &savef); } /* emit_html_img: * The image will be centered in the given box. * Scaling is determined by either the image's scale attribute, * or the imagescale attribute of the graph object being drawn. */ static void emit_html_img(GVJ_t * job, htmlimg_t * cp, htmlenv_t * env) { pointf A[4]; boxf bb = cp->box; char* scale; bb.LL.x += env->pos.x; bb.LL.y += env->pos.y; bb.UR.x += env->pos.x; bb.UR.y += env->pos.y; A[0] = bb.UR; A[2] = bb.LL; A[1].x = A[2].x; A[1].y = A[0].y; A[3].x = A[0].x; A[3].y = A[2].y; if (cp->scale) scale = cp->scale; else scale = env->imgscale; gvrender_usershape(job, cp->src, A, 4, TRUE, scale); } static void emit_html_cell(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env) { htmlmap_data_t saved; boxf pts = cp->data.box; pointf pos = env->pos; int inAnchor, doAnchor = (cp->data.href || cp->data.target); pts.LL.x += pos.x; pts.UR.x += pos.x; pts.LL.y += pos.y; pts.UR.y += pos.y; if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST)) inAnchor = initAnchor(job, env, &cp->data, pts, &saved, 1); else inAnchor = 0; if (cp->data.bgcolor) { doFill(job, cp->data.bgcolor, pts); } if (cp->data.border) doBorder(job, cp->data.pencolor, cp->data.border, pts); if (cp->child.kind == HTML_TBL) emit_html_tbl(job, cp->child.u.tbl, env); else if (cp->child.kind == HTML_IMAGE) emit_html_img(job, cp->child.u.img, env); else emit_html_txt(job, cp->child.u.txt, env); if (inAnchor) endAnchor (job, &saved, 1); if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) { if (initAnchor(job, env, &cp->data, pts, &saved, 0)) endAnchor (job, &saved, 0); } } /* allocObj: * Push new obj on stack to be used in common by all * html elements with anchors. * This inherits the type, emit_state, and object of the * parent, as well as the url, explicit, target and tooltip. */ static void allocObj (GVJ_t * job) { obj_state_t *obj; obj_state_t *parent; obj = push_obj_state(job); parent = obj->parent; obj->type = parent->type; obj->emit_state = parent->emit_state; switch (obj->type) { case NODE_OBJTYPE : obj->u.n = parent->u.n; break; case ROOTGRAPH_OBJTYPE : obj->u.g = parent->u.g; break; case CLUSTER_OBJTYPE : obj->u.sg = parent->u.sg; break; case EDGE_OBJTYPE : obj->u.e = parent->u.e; break; } obj->url = parent->url; obj->tooltip = parent->tooltip; obj->target = parent->target; obj->explicit_tooltip = parent->explicit_tooltip; } static void freeObj (GVJ_t * job) { obj_state_t *obj = job->obj; obj->url = NULL; obj->tooltip = NULL; obj->target = NULL; obj->id = NULL; pop_obj_state(job); } /* emit_html_label: */ void emit_html_label(GVJ_t * job, htmllabel_t * lp, textlabel_t * tp) { htmlenv_t env; allocObj (job); env.pos = tp->pos; env.finfo.color = tp->fontcolor; env.finfo.name = tp->fontname; env.finfo.size = tp->fontsize; env.finfo.size = tp->fontsize; env.imgscale = agget (job->obj->u.n, "imagescale"); env.objid = job->obj->id; env.objid_set = 0; if ((env.imgscale == NULL) || (env.imgscale[0] == '\0')) env.imgscale = "false"; if (lp->kind == HTML_TBL) { htmltbl_t *tbl = lp->u.tbl; /* set basic graphics context */ /* Need to override line style set by node. */ gvrender_set_style(job, job->gvc->defaultlinestyle); if (tbl->data.pencolor) gvrender_set_pencolor(job, tbl->data.pencolor); else gvrender_set_pencolor(job, DEFAULT_COLOR); emit_html_tbl(job, tbl, &env); } else { emit_html_txt(job, lp->u.txt, &env); } if (env.objid_set) free (env.objid); freeObj (job); } void free_html_font(htmlfont_t * fp) { fp->cnt--; if (fp->cnt == 0) { if (fp->name) free(fp->name); if (fp->color) free(fp->color); free(fp); } } void free_html_data(htmldata_t * dp) { free(dp->href); free(dp->port); free(dp->target); free(dp->id); free(dp->title); free(dp->bgcolor); free(dp->pencolor); } void free_html_text(htmltxt_t* t) { htextpara_t *tl; textpara_t *ti; int i, j; if (!t) return; tl = t->paras; for (i = 0; i < t->nparas; i++) { ti = tl->items; for (j = 0; j < tl->nitems; j++) { if (ti->str) free (ti->str); if (ti->font) free_html_font(ti->font); if (ti->layout && ti->free_layout) ti->free_layout (ti->layout); ti++; } tl++; } if (t->paras) free(t->paras); free(t); } void free_html_img(htmlimg_t * ip) { free(ip->src); free(ip); } static void free_html_cell(htmlcell_t * cp) { free_html_label(&cp->child, 0); free_html_data(&cp->data); free(cp); } /* free_html_tbl: * If tbl->n_rows is negative, table is in initial state from * HTML parse, with data stored in u.p. Once run through processTable, * data is stored in u.n and tbl->n_rows is > 0. */ static void free_html_tbl(htmltbl_t * tbl) { htmlcell_t **cells; if (tbl->rc == -1) { dtclose(tbl->u.p.rows); } else { cells = tbl->u.n.cells; free(tbl->heights); free(tbl->widths); while (*cells) { free_html_cell(*cells); cells++; } free(tbl->u.n.cells); } if (tbl->font) free_html_font(tbl->font); free_html_data(&tbl->data); free(tbl); } void free_html_label(htmllabel_t * lp, int root) { if (lp->kind == HTML_TBL) free_html_tbl(lp->u.tbl); else if (lp->kind == HTML_IMAGE) free_html_img(lp->u.img); else free_html_text(lp->u.txt); if (root) free(lp); } static htmldata_t* portToTbl(htmltbl_t *, char *); /* forward declaration */ static htmldata_t* portToCell(htmlcell_t * cp, char *id) { htmldata_t* rv; if (cp->data.port && (strcasecmp(cp->data.port, id) == 0)) rv = &cp->data; else if (cp->child.kind == HTML_TBL) rv = portToTbl(cp->child.u.tbl, id); else rv = NULL; return rv; } /* portToTbl: * See if tp or any of its child cells has the given port id. * If true, return corresponding box. */ static htmldata_t* portToTbl(htmltbl_t* tp, char* id) { htmldata_t* rv; htmlcell_t** cells; htmlcell_t* cp; if (tp->data.port && (strcasecmp(tp->data.port, id) == 0)) rv = &tp->data; else { rv = NULL; cells = tp->u.n.cells; while ((cp = *cells++)) { if ((rv = portToCell(cp, id))) break; } } return rv; } /* html_port: * See if edge port corresponds to part of the html node. * Assume pname != "". * If successful, return pointer to port's box. * Else return NULL. */ boxf *html_port(node_t * n, char *pname, int* sides) { htmldata_t* tp; htmllabel_t* lbl = ND_label(n)->u.html; boxf* rv = NULL; if (lbl->kind == HTML_TEXT) return NULL; tp = portToTbl(lbl->u.tbl, pname); if (tp) { rv = &tp->box; *sides = tp->sides; } return rv; } /* html_path: * Return a box in a table containing the given endpoint. * If the top flow is text (no internal structure), return * the box of the flow * Else return the box of the subtable containing the point. * Because of spacing, the point might not be in any subtable. * In that case, return the top flow's box. * Note that box[0] must contain the edge point. Additional boxes * move out to the boundary. * * At present, unimplemented, since the label may be inside a * non-box node and we need to figure out what this means. */ int html_path(node_t * n, port* p, int side, boxf * rv, int *k) { #ifdef UNIMPL point p; tbl_t *info; tbl_t *t; boxf b; int i; info = (tbl_t *) ND_shape_info(n); assert(info->tbls); info = info->tbls[0]; /* top-level flow */ assert(IS_FLOW(info)); b = info->box; if (info->tbl) { info = info->tbl; if (pt == 1) p = ED_tail_port(e).p; else p = ED_head_port(e).p; p = flip_pt(p, GD_rankdir(n->graph)); /* move p to node's coordinate system */ for (i = 0; (t = info->tbls[i]) != 0; i++) if (INSIDE(p, t->box)) { b = t->box; break; } } /* move box into layout coordinate system */ if (GD_flip(n->graph)) b = flip_trans_box(b, ND_coord_i(n)); else b = move_box(b, ND_coord_i(n)); *k = 1; *rv = b; if (pt == 1) return BOTTOM; else return TOP; #endif return 0; } static int size_html_txt(graph_t *g, htmltxt_t* ftxt, htmlenv_t* env) { double xsize = 0.0; /* width of text block */ double ysize = 0.0; /* height of text block */ double fsize; double lsize; /* height of current line */ double mxfsize = 0.0; /* max. font size for the current line */ double curbline = 0.0; /* dist. of current base line from top */ pointf sz; int i, j, w, width; char *fname; textpara_t lp; htmlfont_t lhf; double maxoffset; lp.font = &lhf; for (i = 0; i < ftxt->nparas; i++) { width = w = 0; maxoffset = mxfsize = 0; for (j = 0; j < ftxt->paras[i].nitems; j++) { lp.str = strdup_and_subst_obj (ftxt->paras[i].items[j].str, env->obj); if (ftxt->paras[i].items[j].font) { if(ftxt->paras[i].items[j].font->flags) lp.font->flags = ftxt->paras[i].items[j].font->flags; else if(env->finfo.flags > 0) lp.font->flags = env->finfo.flags; else lp.font->flags = 0; if (ftxt->paras[i].items[j].font->size > 0) fsize = ftxt->paras[i].items[j].font->size; else fsize = env->finfo.size; if (ftxt->paras[i].items[j].font->name) fname = ftxt->paras[i].items[j].font->name; else fname = env->finfo.name; } else { fsize = env->finfo.size; fname = env->finfo.name; lp.font->flags = 0; } sz = textsize(g, &lp, fname, fsize); free (ftxt->paras[i].items[j].str); ftxt->paras[i].items[j].str = lp.str; ftxt->paras[i].items[j].size = sz.x; ftxt->paras[i].items[j].yoffset_layout = lp.yoffset_layout; ftxt->paras[i].items[j].yoffset_centerline = lp.yoffset_centerline; ftxt->paras[i].items[j].postscript_alias = lp.postscript_alias; ftxt->paras[i].items[j].layout = lp.layout; ftxt->paras[i].items[j].free_layout = lp.free_layout; width += sz.x; mxfsize = MAX(fsize, mxfsize); maxoffset = MAX(lp.yoffset_centerline, maxoffset); } /* lsize = mxfsize * LINESPACING; */ lsize = mxfsize; ftxt->paras[i].size = (double) width; /* ysize - curbline is the distance from the previous * baseline to the bottom of the previous line. * Then, in the current line, we set the baseline to * be 5/6 of the max. font size. Thus, lfsize gives the * distance from the previous baseline to the new one. */ /* ftxt->paras[i].lfsize = 5*mxfsize/6 + ysize - curbline; */ ftxt->paras[i].lfsize = mxfsize + ysize - curbline - maxoffset; curbline += ftxt->paras[i].lfsize; xsize = MAX(width, xsize); ysize += lsize; } ftxt->box.UR.x = xsize; if (ftxt->nparas == 1) ftxt->box.UR.y = (int) (mxfsize); else ftxt->box.UR.y = (int) (ysize); return 0; } /* forward declarion for recursive usage */ static int size_html_tbl(graph_t *g, htmltbl_t * tbl, htmlcell_t * parent, htmlenv_t * env); /* size_html_img: */ static int size_html_img(htmlimg_t * img, htmlenv_t * env) { box b; int rv; b.LL.x = b.LL.y = 0; b.UR = gvusershape_size(env->g, img->src); if ((b.UR.x == -1) && (b.UR.y == -1)) { rv = 1; b.UR.x = b.UR.y = 0; agerr(AGERR, "No or improper image file=\"%s\"\n", img->src); } else { rv = 0; GD_has_images(env->g) = TRUE; } B2BF(b, img->box); return rv; } /* size_html_cell: */ static int size_html_cell(graph_t *g, htmlcell_t * cp, htmltbl_t * parent, htmlenv_t * env) { int rv; pointf sz, child_sz; int margin; cp->parent = parent; if (!(cp->data.flags & PAD_SET)) { if (parent->data.flags & PAD_SET) cp->data.pad = parent->data.pad; else cp->data.pad = DEFAULT_CELLPADDING; } if (!(cp->data.flags & BORDER_SET)) { if (parent->cb >= 0) cp->data.border = parent->cb; else if (parent->data.flags & BORDER_SET) cp->data.border = parent->data.border; else cp->data.border = DEFAULT_BORDER; } if (cp->child.kind == HTML_TBL) { rv = size_html_tbl(g, cp->child.u.tbl, cp, env); child_sz = cp->child.u.tbl->data.box.UR; } else if (cp->child.kind == HTML_IMAGE) { rv = size_html_img(cp->child.u.img, env); child_sz = cp->child.u.img->box.UR; } else { rv = size_html_txt(g, cp->child.u.txt, env); child_sz = cp->child.u.txt->box.UR; } margin = 2 * (cp->data.pad + cp->data.border); sz.x = child_sz.x + margin; sz.y = child_sz.y + margin; if (cp->data.flags & FIXED_FLAG) { if (cp->data.width && cp->data.height) { if ((cp->data.width < sz.x) || (cp->data.height < sz.y)) { agerr(AGWARN, "cell size too small for content\n"); rv = 1; } sz.x = sz.y = 0; } else { agerr(AGWARN, "fixed cell size with unspecified width or height\n"); rv = 1; } } cp->data.box.UR.x = MAX(sz.x, cp->data.width); cp->data.box.UR.y = MAX(sz.y, cp->data.height); return rv; } static int findCol(PointSet * ps, int row, int col, htmlcell_t * cellp) { int notFound = 1; int lastc; int i, j, c; int end = cellp->cspan - 1; while (notFound) { lastc = col + end; for (c = lastc; c >= col; c--) { if (isInPS(ps, c, row)) break; } if (c >= col) /* conflict : try column after */ col = c + 1; else notFound = 0; } for (j = col; j < col + cellp->cspan; j++) { for (i = row; i < row + cellp->rspan; i++) { addPS(ps, j, i); } } return col; } /* processTbl: * Convert parser representation of cells into final form. * Find column and row positions of cells. * Recursively size cells. * Return 1 if problem sizing a cell. */ static int processTbl(graph_t *g, htmltbl_t * tbl, htmlenv_t * env) { pitem *rp; pitem *cp; Dt_t *cdict; int r, c, cnt; htmlcell_t *cellp; htmlcell_t **cells; Dt_t *rows = tbl->u.p.rows; int rv = 0; int n_rows = 0; int n_cols = 0; PointSet *ps = newPS(); rp = (pitem *) dtflatten(rows); cnt = 0; while (rp) { cdict = rp->u.rp; cp = (pitem *) dtflatten(cdict); while (cp) { cellp = cp->u.cp; cnt++; cp = (pitem *) dtlink(cdict, (Dtlink_t *) cp); } rp = (pitem *) dtlink(rows, (Dtlink_t *) rp); } cells = tbl->u.n.cells = N_NEW(cnt + 1, htmlcell_t *); rp = (pitem *) dtflatten(rows); r = 0; while (rp) { cdict = rp->u.rp; cp = (pitem *) dtflatten(cdict); c = 0; while (cp) { cellp = cp->u.cp; *cells++ = cellp; rv |= size_html_cell(g, cellp, tbl, env); c = findCol(ps, r, c, cellp); cellp->row = r; cellp->col = c; c += cellp->cspan; n_cols = MAX(c, n_cols); n_rows = MAX(r + cellp->rspan, n_rows); cp = (pitem *) dtlink(cdict, (Dtlink_t *) cp); } rp = (pitem *) dtlink(rows, (Dtlink_t *) rp); r++; } tbl->rc = n_rows; tbl->cc = n_cols; dtclose(rows); freePS(ps); return rv; } /* Split size x over n pieces with spacing s. * We substract s*(n-1) from x, divide by n and * take the ceiling. */ #define SPLIT(x,n,s) (((x) - ((s)-1)*((n)-1)) / (n)) /* sizeLinearArray: * Determine sizes of rows and columns. The size of a column is the * maximum width of any cell in it. Similarly for rows. * A cell spanning columns contributes proportionately to each column * it is in. */ void sizeLinearArray(htmltbl_t * tbl) { htmlcell_t *cp; htmlcell_t **cells; int wd, ht, i, x, y; tbl->heights = N_NEW(tbl->rc + 1, int); tbl->widths = N_NEW(tbl->cc + 1, int); for (cells = tbl->u.n.cells; *cells; cells++) { cp = *cells; if (cp->rspan == 1) ht = cp->data.box.UR.y; else { ht = SPLIT(cp->data.box.UR.y, cp->rspan, tbl->data.space); ht = MAX(ht, 1); } if (cp->cspan == 1) wd = cp->data.box.UR.x; else { wd = SPLIT(cp->data.box.UR.x, cp->cspan, tbl->data.space); wd = MAX(wd, 1); } for (i = cp->row; i < cp->row + cp->rspan; i++) { y = tbl->heights[i]; tbl->heights[i] = MAX(y, ht); } for (i = cp->col; i < cp->col + cp->cspan; i++) { x = tbl->widths[i]; tbl->widths[i] = MAX(x, wd); } } } static char *nnames[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", }; /* nToName: * Convert int to its decimal string representation. */ char *nToName(int c) { static char name[100]; if (c < sizeof(nnames) / sizeof(char *)) return nnames[c]; sprintf(name, "%d", c); return name; } /* closeGraphs: * Clean up graphs made for setting column and row widths. */ static void closeGraphs(graph_t * rowg, graph_t * colg) { node_t *n; for (n = GD_nlist(colg); n; n = ND_next(n)) { free_list(ND_in(n)); free_list(ND_out(n)); } agclose(rowg); agclose(colg); } static void checkChain(graph_t * g) { node_t *t; node_t *h; edge_t *e; t = GD_nlist(g); for (h = ND_next(t); h; h = ND_next(h)) { if (!agfindedge(g, t, h)) { #ifdef WITH_CGRAPH e = agedge(g, t, h, NULL, 1); #else e = agedge(g, t, h); #endif ED_minlen(e) = 0; elist_append(e, ND_out(t)); elist_append(e, ND_in(h)); } t = h; } } /* makeGraphs: * Generate dags modeling the row and column constraints. * If the table has cc columns, we create the graph * 0 -> 1 -> 2 -> ... -> cc * and if a cell starts in column c with span cspan, with * width w, we add the edge c -> c+cspan [minlen = w]. * * We might simplify the graph by removing multiedges, * using the max minlen, but will affect the balancing? */ void makeGraphs(htmltbl_t * tbl, graph_t * rowg, graph_t * colg) { htmlcell_t *cp; htmlcell_t **cells; node_t *t; node_t *lastn; node_t *h; edge_t *e; int i; int* minc; int* minr; lastn = NULL; for (i = 0; i <= tbl->cc; i++) { #ifdef WITH_CGRAPH t = agnode(colg, nToName(i),1); #else t = agnode(colg, nToName(i)); #endif alloc_elist(tbl->rc, ND_in(t)); alloc_elist(tbl->rc, ND_out(t)); if (lastn) { ND_next(lastn) = t; lastn = t; } else { lastn = GD_nlist(colg) = t; } } lastn = NULL; for (i = 0; i <= tbl->rc; i++) { #ifdef WITH_CGRAPH t = agnode(rowg, nToName(i),1); #else t = agnode(rowg, nToName(i)); #endif alloc_elist(tbl->cc, ND_in(t)); alloc_elist(tbl->cc, ND_out(t)); if (lastn) { ND_next(lastn) = t; lastn = t; } else { lastn = GD_nlist(rowg) = t; } } minr = N_NEW(tbl->rc, int); minc = N_NEW(tbl->cc, int); for (cells = tbl->u.n.cells; *cells; cells++) { int x, y, c, r; cp = *cells; x = (cp->data.box.UR.x + (cp->cspan-1))/cp->cspan; for (c = 0; c < cp->cspan; c++) minc[cp->col + c] = MAX(minc[cp->col + c],x); y = (cp->data.box.UR.y + (cp->rspan-1))/cp->rspan; for (r = 0; r < cp->rspan; r++) minr[cp->row + r] = MAX(minr[cp->row + r],y); } for (cells = tbl->u.n.cells; *cells; cells++) { int x, y, c, r; cp = *cells; t = agfindnode(colg, nToName(cp->col)); h = agfindnode(colg, nToName(cp->col + cp->cspan)); #ifdef WITH_CGRAPH e = agedge(colg, t, h, NULL, 1); #else e = agedge(colg, t, h); #endif x = 0; for (c = 0; c < cp->cspan; c++) x += minc[cp->col + c]; ED_minlen(e) = x; /* ED_minlen(e) = cp->data.box.UR.x; */ #if (DEBUG==2) fprintf(stderr, "col edge %s -> %s %d\n", t->name, h->name, ED_minlen(e)); #endif elist_append(e, ND_out(t)); elist_append(e, ND_in(h)); t = agfindnode(rowg, nToName(cp->row)); h = agfindnode(rowg, nToName(cp->row + cp->rspan)); #ifdef WITH_CGRAPH e = agedge(rowg, t, h, NULL, 1); #else e = agedge(rowg, t, h); #endif y = 0; for (r = 0; r < cp->rspan; r++) y += minr[cp->row + r]; ED_minlen(e) = y; /* ED_minlen(e) = cp->data.box.UR.y; */ #if (DEBUG==2) fprintf(stderr, "row edge %s -> %s %d\n", agnameof(t), agnameof(h), ED_minlen(e)); #endif elist_append(e, ND_out(t)); elist_append(e, ND_in(h)); } /* Make sure that 0 <= 1 <= 2 ...k. This implies graph connected. */ checkChain(colg); checkChain(rowg); free (minc); free (minr); } /* setSizes: * Use rankings to determine cell dimensions. The rank values * give the coordinate, so to get the width/height, we have * to subtract the previous value. */ void setSizes(htmltbl_t * tbl, graph_t * rowg, graph_t * colg) { int i; node_t *n; int prev; prev = 0; n = GD_nlist(rowg); for (i = 0, n = ND_next(n); n; i++, n = ND_next(n)) { tbl->heights[i] = ND_rank(n) - prev; prev = ND_rank(n); } prev = 0; n = GD_nlist(colg); for (i = 0, n = ND_next(n); n; i++, n = ND_next(n)) { tbl->widths[i] = ND_rank(n) - prev; prev = ND_rank(n); } } /* sizeArray: * Set column and row sizes. Optimize for minimum width and * height. Where there is slack, try to distribute evenly. * We do this by encoding cells as edges with min length is * a dag on a chain. We then run network simplex, using * LR_balance. */ void sizeArray(htmltbl_t * tbl) { graph_t *rowg; graph_t *colg; /* Do the 1D cases by hand */ if ((tbl->rc == 1) || (tbl->cc == 1)) { sizeLinearArray(tbl); return; } tbl->heights = N_NEW(tbl->rc + 1, int); tbl->widths = N_NEW(tbl->cc + 1, int); #ifdef WITH_CGRAPH rowg = agopen("rowg", Agdirected,NIL(Agdisc_t *)); colg = agopen("colg", Agdirected,NIL(Agdisc_t *)); #else rowg = agopen("rowg", AGDIGRAPH); colg = agopen("colg", AGDIGRAPH); #endif makeGraphs(tbl, rowg, colg); rank(rowg, 2, INT_MAX); rank(colg, 2, INT_MAX); setSizes(tbl, rowg, colg); closeGraphs(rowg, colg); } static void pos_html_tbl(htmltbl_t *, boxf, int); /* forward declaration */ /* pos_html_img: * Place image in cell * storing allowed space handed by parent cell. * How this space is used is handled in emit_html_img. */ static void pos_html_img(htmlimg_t * cp, boxf pos) { cp->box = pos; } /* pos_html_txt: * Set default alignment. */ static void pos_html_txt(htmltxt_t* ftxt, char c) { int i; for (i = 0; i < ftxt->nparas; i++) { if (ftxt->paras[i].just == UNSET_ALIGN) /* unset */ ftxt->paras[i].just = c; } } /* pos_html_cell: */ static void pos_html_cell(htmlcell_t * cp, boxf pos, int sides) { double delx, dely; pointf oldsz; boxf cbox; if (!cp->data.pencolor && cp->parent->data.pencolor) cp->data.pencolor = strdup(cp->parent->data.pencolor); /* If fixed, align cell */ if (cp->data.flags & FIXED_FLAG) { oldsz = cp->data.box.UR; delx = (pos.UR.x - pos.LL.x) - oldsz.x; if (delx > 0) { switch (cp->data.flags & HALIGN_MASK) { case HALIGN_LEFT: pos.UR.x = pos.LL.x + oldsz.x; break; case HALIGN_RIGHT: pos.UR.x += delx; pos.LL.x += delx; break; default: pos.LL.x += delx / 2; pos.UR.x -= delx / 2; break; } } dely = (pos.UR.y - pos.LL.y) - oldsz.y; if (dely > 0) { switch (cp->data.flags & VALIGN_MASK) { case VALIGN_BOTTOM: pos.UR.y = pos.LL.y + oldsz.y; break; case VALIGN_TOP: pos.UR.y += dely; pos.LL.y += dely; break; default: pos.LL.y += dely / 2; pos.UR.y -= dely / 2; break; } } } cp->data.box = pos; cp->data.sides = sides; /* set up child's position */ cbox.LL.x = pos.LL.x + cp->data.border + cp->data.pad; cbox.LL.y = pos.LL.y + cp->data.border + cp->data.pad; cbox.UR.x = pos.UR.x - cp->data.border - cp->data.pad; cbox.UR.y = pos.UR.y - cp->data.border - cp->data.pad; if (cp->child.kind == HTML_TBL) { pos_html_tbl(cp->child.u.tbl, cbox, sides); } else if (cp->child.kind == HTML_IMAGE) { /* Note that alignment trumps scaling */ oldsz = cp->child.u.img->box.UR; delx = (cbox.UR.x - cbox.LL.x) - oldsz.x; if (delx > 0) { switch (cp->data.flags & HALIGN_MASK) { case HALIGN_LEFT: cbox.UR.x -= delx; break; case HALIGN_RIGHT: cbox.LL.x += delx; break; } } dely = (cbox.UR.y - cbox.LL.y) - oldsz.y; if (dely > 0) { switch (cp->data.flags & VALIGN_MASK) { case VALIGN_BOTTOM: cbox.UR.y -= dely; break; case VALIGN_TOP: cbox.LL.y += dely; break; } } pos_html_img(cp->child.u.img, cbox); } else { char dfltalign; int af; oldsz = cp->child.u.txt->box.UR; delx = (cbox.UR.x - cbox.LL.x) - oldsz.x; /* If the cell is larger than the text block and alignment is * done at textblock level, the text box is shrunk accordingly. */ if ((delx > 0)&&((af=(cp->data.flags & HALIGN_MASK)) != HALIGN_TEXT)) { switch (af) { case HALIGN_LEFT: cbox.UR.x -= delx; break; case HALIGN_RIGHT: cbox.LL.x += delx; break; default: cbox.LL.x += delx / 2; cbox.UR.x -= delx / 2; break; } } dely = (cbox.UR.y - cbox.LL.y) - oldsz.y; if (dely > 0) { switch (cp->data.flags & VALIGN_MASK) { case VALIGN_BOTTOM: cbox.UR.y -= dely; break; case VALIGN_TOP: cbox.LL.y += dely; break; default: cbox.LL.y += dely / 2; cbox.UR.y -= dely / 2; break; } } cp->child.u.txt->box = cbox; /* Set default text alignment */ switch (cp->data.flags & BALIGN_MASK) { case BALIGN_LEFT: dfltalign = 'l'; break; case BALIGN_RIGHT: dfltalign = 'r'; break; default: dfltalign = 'n'; break; } pos_html_txt (cp->child.u.txt, dfltalign); } } /* pos_html_tbl: * Position table given its box, then calculate * the position of each cell. In addition, set the sides * attribute indicating which external sides of the node * are accessible to the table. */ static void pos_html_tbl(htmltbl_t * tbl, boxf pos, int sides) { int x, y, delx, dely, oldsz; int i, extra, plus; htmlcell_t **cells = tbl->u.n.cells; htmlcell_t *cp; boxf cbox; if (tbl->u.n.parent && tbl->u.n.parent->data.pencolor && !tbl->data.pencolor) tbl->data.pencolor = strdup (tbl->u.n.parent->data.pencolor); oldsz = tbl->data.box.UR.x; delx = (pos.UR.x - pos.LL.x) - oldsz; assert(delx >= 0); oldsz = tbl->data.box.UR.y; dely = (pos.UR.y - pos.LL.y) - oldsz; assert(dely >= 0); /* If fixed, align box */ if (tbl->data.flags & FIXED_FLAG) { if (delx > 0) { switch (tbl->data.flags & HALIGN_MASK) { case HALIGN_LEFT: pos.UR.x = pos.LL.x + oldsz; break; case HALIGN_RIGHT: pos.UR.x += delx; pos.LL.x += delx; break; default: pos.LL.x += delx / 2; pos.UR.x -= delx / 2; break; } delx = 0; } if (dely > 0) { switch (tbl->data.flags & VALIGN_MASK) { case VALIGN_BOTTOM: pos.UR.y = pos.LL.y + oldsz; break; case VALIGN_TOP: pos.UR.y += dely; pos.LL.y += dely; break; default: pos.LL.y += dely / 2; pos.UR.y -= dely / 2; break; } dely = 0; } } /* change sizes to start positions and distribute extra space */ x = pos.LL.x + tbl->data.border + tbl->data.space; extra = delx / (tbl->cc); plus = ROUND(delx - extra * (tbl->cc)); for (i = 0; i <= tbl->cc; i++) { delx = tbl->widths[i] + extra + (i < plus ? 1 : 0); tbl->widths[i] = x; x += delx + tbl->data.space; } y = pos.UR.y - tbl->data.border - tbl->data.space; extra = dely / (tbl->rc); plus = ROUND(dely - extra * (tbl->rc)); for (i = 0; i <= tbl->rc; i++) { dely = tbl->heights[i] + extra + (i < plus ? 1 : 0); tbl->heights[i] = y; y -= dely + tbl->data.space; } while ((cp = *cells++)) { int mask = 0; if (sides) { if (cp->col == 0) mask |= LEFT; if (cp->row == 0) mask |= TOP; if (cp->col + cp->cspan == tbl->cc) mask |= RIGHT; if (cp->row + cp->rspan == tbl->rc) mask |= BOTTOM; } cbox.LL.x = tbl->widths[cp->col]; cbox.UR.x = tbl->widths[cp->col + cp->cspan] - tbl->data.space; cbox.UR.y = tbl->heights[cp->row]; cbox.LL.y = tbl->heights[cp->row + cp->rspan] + tbl->data.space; pos_html_cell(cp, cbox, sides & mask); } tbl->data.sides = sides; tbl->data.box = pos; } /* size_html_tbl: * Determine the size of a table by first determining the * size of each cell. */ static int size_html_tbl(graph_t *g, htmltbl_t * tbl, htmlcell_t * parent, htmlenv_t * env) { int i, wd, ht; int rv = 0; static htmlfont_t savef; if (tbl->font) pushFontInfo(env, tbl->font, &savef); tbl->u.n.parent = parent; rv = processTbl(g, tbl, env); /* Set up border and spacing */ if (!(tbl->data.flags & SPACE_SET)) { tbl->data.space = DEFAULT_CELLSPACING; } if (!(tbl->data.flags & BORDER_SET)) { tbl->data.border = DEFAULT_BORDER; } sizeArray(tbl); wd = (tbl->cc + 1) * tbl->data.space + 2 * tbl->data.border; ht = (tbl->rc + 1) * tbl->data.space + 2 * tbl->data.border; for (i = 0; i < tbl->cc; i++) wd += tbl->widths[i]; for (i = 0; i < tbl->rc; i++) ht += tbl->heights[i]; if (tbl->data.flags & FIXED_FLAG) { if (tbl->data.width && tbl->data.height) { if ((tbl->data.width < wd) || (tbl->data.height < ht)) { agerr(AGWARN, "table size too small for content\n"); rv = 1; } wd = ht = 0; } else { agerr(AGWARN, "fixed table size with unspecified width or height\n"); rv = 1; } } tbl->data.box.UR.x = MAX(wd, tbl->data.width); tbl->data.box.UR.y = MAX(ht, tbl->data.height); if (tbl->font) popFontInfo(env, &savef); return rv; } static char *nameOf(void *obj, agxbuf * xb) { Agedge_t *ep; switch (agobjkind(obj)) { #ifndef WITH_CGRAPH case AGGRAPH: #else case AGRAPH: #endif agxbput(xb, agnameof(((Agraph_t *) obj))); break; case AGNODE: agxbput(xb, agnameof(((Agnode_t *) obj))); break; case AGEDGE: ep = (Agedge_t *) obj; agxbput(xb, agnameof(agtail(ep))); agxbput(xb, agnameof(aghead(ep))); if (agisdirected(agraphof(aghead(ep)))) agxbput(xb, "->"); else agxbput(xb, "--"); break; } return agxbuse(xb); } #ifdef DEBUG void indent(int i) { while (i--) fprintf(stderr, " "); } void printBox(boxf b) { fprintf(stderr, "(%f,%f)(%f,%f)", b.LL.x, b.LL.y, b.UR.x, b.UR.y); } void printImage(htmlimg_t *ip, int ind) { indent(ind); fprintf(stderr, "img: %s\n", ip->src); } void printTxt(htmltxt_t * txt, int ind) { int i, j; indent(ind); fprintf (stderr, "txt paras = %d \n", txt->nparas); for (i = 0; i < txt->nparas; i++) { indent(ind+1); fprintf (stderr, "[%d] %d items\n", i, txt->paras[i].nitems); for (j = 0; j < txt->paras[i].nitems; j++) { indent(ind+2); fprintf (stderr, "[%d] (%f) \"%s\" ", j, txt->paras[i].items[j].size, txt->paras[i].items[j].str); if (txt->paras[i].items[j].font) fprintf (stderr, "font %s color %s size %f\n", txt->paras[i].items[j].font->name, txt->paras[i].items[j].font->color, txt->paras[i].items[j].font->size); else fprintf (stderr, "\n"); } } } void printData(htmldata_t * dp) { unsigned char flags = dp->flags; char c; fprintf(stderr, "s%d(%d) ", dp->space, (flags & SPACE_SET ? 1 : 0)); fprintf(stderr, "b%d(%d) ", dp->border, (flags & BORDER_SET ? 1 : 0)); fprintf(stderr, "p%d(%d) ", dp->pad, (flags & PAD_SET ? 1 : 0)); switch (flags & HALIGN_MASK) { case HALIGN_RIGHT: c = 'r'; break; case HALIGN_LEFT: c = 'l'; break; default: c = 'n'; break; } fprintf(stderr, "%c", c); switch (flags & VALIGN_MASK) { case VALIGN_TOP: c = 't'; break; case VALIGN_BOTTOM: c = 'b'; break; default: c = 'c'; break; } fprintf(stderr, "%c ", c); printBox(dp->box); } void printTbl(htmltbl_t * tbl, int ind) { htmlcell_t **cells = tbl->u.n.cells; indent(ind); fprintf(stderr, "tbl (%p) %d %d ", tbl, tbl->cc, tbl->rc); printData(&tbl->data); fputs("\n", stderr); while (*cells) printCell(*cells++, ind + 1); } static void printCell(htmlcell_t * cp, int ind) { indent(ind); fprintf(stderr, "cell %d %d %d %d ", cp->cspan, cp->rspan, cp->col, cp->row); printData(&cp->data); fputs("\n", stderr); switch (cp->child.kind) { case HTML_TBL: printTbl(cp->child.u.tbl, ind + 1); break; case HTML_TEXT: printTxt(cp->child.u.txt, ind + 1); break; case HTML_IMAGE: printImage(cp->child.u.img, ind + 1); break; default: break; } } void printLbl(htmllabel_t * lbl) { if (lbl->kind == HTML_TBL) printTbl(lbl->u.tbl, 0); else printTxt(lbl->u.txt, 0); } #endif /* DEBUG */ static char *getPenColor(void *obj) { char *str; if (((str = agget(obj, "pencolor")) != 0) && str[0]) return str; else if (((str = agget(obj, "color")) != 0) && str[0]) return str; else return NULL; } /* make_html_label: * Return non-zero if problem parsing HTML. In this case, use object name. */ int make_html_label(void *obj, textlabel_t * lp) { int rv; double wd2, ht2; boxf box; graph_t *g; htmllabel_t *lbl; htmlenv_t env; char *s; env.obj = obj; switch (agobjkind(obj)) { #ifdef WITH_CGRAPH case AGRAPH: #else case AGGRAPH: #endif env.g = ((Agraph_t *) obj)->root; break; case AGNODE: env.g = agraphof(((Agnode_t *) obj)); break; case AGEDGE: env.g = agraphof(aghead (((Agedge_t *) obj))); break; } g = env.g->root; env.finfo.size = lp->fontsize; env.finfo.name = lp->fontname; env.finfo.color = lp->fontcolor; lbl = parseHTML(lp->text, &rv, GD_charset(env.g)); if (!lbl) { /* Parse of label failed; revert to simple text label */ agxbuf xb; unsigned char buf[SMALLBUF]; agxbinit(&xb, SMALLBUF, buf); lp->html = FALSE; lp->text = strdup(nameOf(obj, &xb)); switch (lp->charset) { case CHAR_LATIN1: s = latin1ToUTF8(lp->text); break; default: /* UTF8 */ s = htmlEntityUTF8(lp->text); break; } free(lp->text); lp->text = s; make_simple_label(g, lp); agxbfree(&xb); return rv; } if (lbl->kind == HTML_TBL) { if (! lbl->u.tbl->data.pencolor && getPenColor(obj)) lbl->u.tbl->data.pencolor = strdup(getPenColor(obj)); rv |= size_html_tbl(g, lbl->u.tbl, NULL, &env); wd2 = (lbl->u.tbl->data.box.UR.x + 1) / 2; ht2 = (lbl->u.tbl->data.box.UR.y + 1) / 2; box = boxfof(-wd2, -ht2, wd2, ht2); pos_html_tbl(lbl->u.tbl, box, BOTTOM | RIGHT | TOP | LEFT); lp->dimen.x = box.UR.x - box.LL.x; lp->dimen.y = box.UR.y - box.LL.y; } else { rv |= size_html_txt(g, lbl->u.txt, &env); wd2 = (lbl->u.txt->box.UR.x + 1) / 2; ht2 = (lbl->u.txt->box.UR.y + 1) / 2; box = boxfof(-wd2, -ht2, wd2, ht2); lbl->u.txt->box = box; lp->dimen.x = box.UR.x - box.LL.x; lp->dimen.y = box.UR.y - box.LL.y; } lp->u.html = lbl; /* If the label is a table, replace label text because this may * be used for the title and alt fields in image maps. */ if (lbl->kind == HTML_TBL) { free (lp->text); lp->text = strdup ("<TABLE>"); } return rv; }