1/* $Id$Revision: */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: See CVS logs. Details at http://www.graphviz.org/
12 *************************************************************************/
13
14
15/* Implementation of HTML-like tables.
16 *
17 * The (now purged) CodeGen graphics model, especially with integral coodinates, is
18 * not adequate to handle this as we would like. In particular, it is
19 * difficult to handle notions of adjacency and correct rounding to pixels.
20 * For example, if 2 adjacent boxes bb1.UR.x == bb2.LL.x, the rectangles
21 * may be drawn overlapping. However, if we use bb1.UR.x+1 == bb2.LL.x
22 * there may or may not be a gap between them, even in the same device
23 * depending on their positions. When CELLSPACING > 1, this isn't as much
24 * of a problem.
25 *
26 * We allow negative spacing as a hack to allow overlapping cell boundaries.
27 * For the reasons discussed above, this is difficult to get correct.
28 * This is an important enough case we should extend the table model to
29 * support it correctly. This could be done by allowing a table attribute,
30 * e.g., CELLGRID=n, which sets CELLBORDER=0 and has the border drawing
31 * handled correctly by the table.
32 */
33
34#include <assert.h>
35#include "render.h"
36#include "htmltable.h"
37#include "agxbuf.h"
38#include "pointset.h"
39#include "intset.h"
40#include "cdt.h"
41
42#define DEFAULT_BORDER 1
43#define DEFAULT_CELLPADDING 2
44#define DEFAULT_CELLSPACING 2
45
46typedef struct {
47 char *url;
48 char *tooltip;
49 char *target;
50 char *id;
51 boolean explicit_tooltip;
52 point LL;
53 point UR;
54} htmlmap_data_t;
55
56#ifdef DEBUG
57static void printCell(htmlcell_t * cp, int ind);
58#endif
59
60/* pushFontInfo:
61 * Replace current font attributes in env with ones from fp,
62 * storing old attributes in savp. We only deal with attributes
63 * set in env. The attributes are restored via popFontInfo.
64 */
65static void
66pushFontInfo(htmlenv_t * env, textfont_t * fp, textfont_t * savp)
67{
68 if (env->finfo.name) {
69 if (fp->name) {
70 savp->name = env->finfo.name;
71 env->finfo.name = fp->name;
72 } else
73 savp->name = NULL;
74 }
75 if (env->finfo.color) {
76 if (fp->color) {
77 savp->color = env->finfo.color;
78 env->finfo.color = fp->color;
79 } else
80 savp->color = NULL;
81 }
82 if (env->finfo.size >= 0) {
83 if (fp->size >= 0) {
84 savp->size = env->finfo.size;
85 env->finfo.size = fp->size;
86 } else
87 savp->size = -1.0;
88 }
89}
90
91/* popFontInfo:
92 * Restore saved font attributes.
93 * Copy only set values.
94 */
95static void popFontInfo(htmlenv_t * env, textfont_t * savp)
96{
97 if (savp->name)
98 env->finfo.name = savp->name;
99 if (savp->color)
100 env->finfo.color = savp->color;
101 if (savp->size >= 0.0)
102 env->finfo.size = savp->size;
103}
104
105static void
106emit_htextspans(GVJ_t * job, int nspans, htextspan_t * spans, pointf p,
107 double halfwidth_x, textfont_t finfo, boxf b, int simple)
108{
109 int i, j;
110 double center_x, left_x, right_x;
111 textspan_t tl;
112 textfont_t tf;
113 pointf p_ = { 0.0, 0.0 };
114 textspan_t *ti;
115
116 center_x = p.x;
117 left_x = center_x - halfwidth_x;
118 right_x = center_x + halfwidth_x;
119
120 /* Initial p is in center of text block; set initial baseline
121 * to top of text block.
122 */
123 p_.y = p.y + (b.UR.y - b.LL.y) / 2.0;
124
125 gvrender_begin_label(job, LABEL_HTML);
126 for (i = 0; i < nspans; i++) {
127 /* set p.x to leftmost point where the line of text begins */
128 switch (spans[i].just) {
129 case 'l':
130 p.x = left_x;
131 break;
132 case 'r':
133 p.x = right_x - spans[i].size;
134 break;
135 default:
136 case 'n':
137 p.x = center_x - spans[i].size / 2.0;
138 break;
139 }
140 p_.y -= spans[i].lfsize; /* move to current base line */
141
142 ti = spans[i].items;
143 for (j = 0; j < spans[i].nitems; j++) {
144 if (ti->font && (ti->font->size > 0))
145 tf.size = ti->font->size;
146 else
147 tf.size = finfo.size;
148 if (ti->font && ti->font->name)
149 tf.name = ti->font->name;
150 else
151 tf.name = finfo.name;
152 if (ti->font && ti->font->color)
153 tf.color = ti->font->color;
154 else
155 tf.color = finfo.color;
156 if (ti->font && ti->font->flags)
157 tf.flags = ti->font->flags;
158 else
159 tf.flags = 0;
160
161 gvrender_set_pencolor(job, tf.color);
162
163 tl.str = ti->str;
164 tl.font = &tf;
165 tl.yoffset_layout = ti->yoffset_layout;
166 if (simple)
167 tl.yoffset_centerline = ti->yoffset_centerline;
168 else
169 tl.yoffset_centerline = 1;
170 tl.font->postscript_alias = ti->font->postscript_alias;
171 tl.layout = ti->layout;
172 tl.size.x = ti->size.x;
173 tl.size.y = spans[i].lfsize;
174 tl.just = 'l';
175
176 p_.x = p.x;
177 gvrender_textspan(job, p_, &tl);
178 p.x += ti->size.x;
179 ti++;
180 }
181 }
182
183 gvrender_end_label(job);
184}
185
186static void emit_html_txt(GVJ_t * job, htmltxt_t * tp, htmlenv_t * env)
187{
188 double halfwidth_x;
189 pointf p;
190
191 /* make sure that there is something to do */
192 if (tp->nspans < 1)
193 return;
194
195 halfwidth_x = ((double) (tp->box.UR.x - tp->box.LL.x)) / 2.0;
196 p.x = env->pos.x + ((double) (tp->box.UR.x + tp->box.LL.x)) / 2.0;
197 p.y = env->pos.y + ((double) (tp->box.UR.y + tp->box.LL.y)) / 2.0;
198
199 emit_htextspans(job, tp->nspans, tp->spans, p, halfwidth_x, env->finfo,
200 tp->box, tp->simple);
201}
202
203static void doSide(GVJ_t * job, pointf p, double wd, double ht)
204{
205 boxf BF;
206
207 BF.LL = p;
208 BF.UR.x = p.x + wd;
209 BF.UR.y = p.y + ht;
210 gvrender_box(job, BF, 1);
211}
212
213/* mkPts:
214 * Convert boxf into four corner points
215 * If border is > 1, inset the points by half the border.
216 * It is assumed AF is pointf[4], so the data is store there
217 * and AF is returned.
218 */
219static pointf *mkPts(pointf * AF, boxf b, int border)
220{
221 AF[0] = b.LL;
222 AF[2] = b.UR;
223 if (border > 1) {
224 double delta = ((double) border) / 2.0;
225 AF[0].x += delta;
226 AF[0].y += delta;
227 AF[2].x -= delta;
228 AF[2].y -= delta;
229 }
230 AF[1].x = AF[2].x;
231 AF[1].y = AF[0].y;
232 AF[3].x = AF[0].x;
233 AF[3].y = AF[2].y;
234
235 return AF;
236}
237
238/* doBorder:
239 * Draw a rectangular border for the box b.
240 * Handles dashed and dotted styles, rounded corners.
241 * Also handles thick lines.
242 * Assume dp->border > 0
243 */
244static void doBorder(GVJ_t * job, htmldata_t * dp, boxf b)
245{
246 pointf AF[7];
247 char *sptr[2];
248 char *color = (dp->pencolor ? dp->pencolor : DEFAULT_COLOR);
249 unsigned short sides;
250
251 gvrender_set_pencolor(job, color);
252 if ((dp->style & (DASHED | DOTTED))) {
253 sptr[0] = sptr[1] = NULL;
254 if (dp->style & DASHED)
255 sptr[0] = "dashed";
256 else if (dp->style & DOTTED)
257 sptr[0] = "dotted";
258 gvrender_set_style(job, sptr);
259 } else
260 gvrender_set_style(job, job->gvc->defaultlinestyle);
261 gvrender_set_penwidth(job, dp->border);
262
263 if (dp->style & ROUNDED)
264 round_corners(job, mkPts(AF, b, dp->border), 4, ROUNDED, 0);
265 else if ((sides = (dp->flags & BORDER_MASK))) {
266 mkPts (AF+1, b, dp->border); /* AF[1-4] has LL=SW,SE,UR=NE,NW */
267 switch (sides) {
268 case BORDER_BOTTOM :
269 gvrender_polyline(job, AF+1, 2);
270 break;
271 case BORDER_RIGHT :
272 gvrender_polyline(job, AF+2, 2);
273 break;
274 case BORDER_TOP :
275 gvrender_polyline(job, AF+3, 2);
276 break;
277 case BORDER_LEFT :
278 AF[0] = AF[4];
279 gvrender_polyline(job, AF, 2);
280 break;
281 case BORDER_BOTTOM|BORDER_RIGHT :
282 gvrender_polyline(job, AF+1, 3);
283 break;
284 case BORDER_RIGHT|BORDER_TOP :
285 gvrender_polyline(job, AF+2, 3);
286 break;
287 case BORDER_TOP|BORDER_LEFT :
288 AF[5] = AF[1];
289 gvrender_polyline(job, AF+3, 3);
290 break;
291 case BORDER_LEFT|BORDER_BOTTOM :
292 AF[0] = AF[4];
293 gvrender_polyline(job, AF, 3);
294 break;
295 case BORDER_BOTTOM|BORDER_RIGHT|BORDER_TOP :
296 gvrender_polyline(job, AF+1, 4);
297 break;
298 case BORDER_RIGHT|BORDER_TOP|BORDER_LEFT :
299 AF[5] = AF[1];
300 gvrender_polyline(job, AF+2, 4);
301 break;
302 case BORDER_TOP|BORDER_LEFT|BORDER_BOTTOM :
303 AF[5] = AF[1];
304 AF[6] = AF[2];
305 gvrender_polyline(job, AF+3, 4);
306 break;
307 case BORDER_LEFT|BORDER_BOTTOM|BORDER_RIGHT :
308 AF[0] = AF[4];
309 gvrender_polyline(job, AF, 4);
310 break;
311 case BORDER_TOP|BORDER_BOTTOM :
312 gvrender_polyline(job, AF+1, 2);
313 gvrender_polyline(job, AF+3, 2);
314 break;
315 case BORDER_LEFT|BORDER_RIGHT :
316 AF[0] = AF[4];
317 gvrender_polyline(job, AF, 2);
318 gvrender_polyline(job, AF+2, 2);
319 break;
320 }
321 } else {
322 if (dp->border > 1) {
323 double delta = ((double) dp->border) / 2.0;
324 b.LL.x += delta;
325 b.LL.y += delta;
326 b.UR.x -= delta;
327 b.UR.y -= delta;
328 }
329 gvrender_box(job, b, 0);
330 }
331}
332
333/* setFill:
334 * Set up fill values from given color; make pen transparent.
335 * Return type of fill required.
336 */
337static int
338setFill(GVJ_t * job, char *color, int angle, int style, char *clrs[2])
339{
340 int filled;
341 float frac;
342 if (findStopColor(color, clrs, &frac)) {
343 gvrender_set_fillcolor(job, clrs[0]);
344 if (clrs[1])
345 gvrender_set_gradient_vals(job, clrs[1], angle, frac);
346 else
347 gvrender_set_gradient_vals(job, DEFAULT_COLOR, angle, frac);
348 if (style & RADIAL)
349 filled = RGRADIENT;
350 else
351 filled = GRADIENT;
352 } else {
353 gvrender_set_fillcolor(job, color);
354 filled = FILL;
355 }
356 gvrender_set_pencolor(job, "transparent");
357 return filled;
358}
359
360/* initAnchor:
361 * Save current map values.
362 * Initialize fields in job->obj pertaining to anchors.
363 * In particular, this also sets the output rectangle.
364 * If there is something to do,
365 * start the anchor and returns 1.
366 * Otherwise, it returns 0.
367 *
368 * FIX: Should we provide a tooltip if none is set, as is done
369 * for nodes, edges, etc. ?
370 */
371static int
372initAnchor(GVJ_t * job, htmlenv_t * env, htmldata_t * data, boxf b,
373 htmlmap_data_t * save)
374{
375 obj_state_t *obj = job->obj;
376 int changed;
377 char *id;
378 static int anchorId;
379 int internalId = 0;
380 agxbuf xb;
381 char intbuf[30]; /* hold 64-bit decimal integer */
382 unsigned char buf[SMALLBUF];
383
384 save->url = obj->url;
385 save->tooltip = obj->tooltip;
386 save->target = obj->target;
387 save->id = obj->id;
388 save->explicit_tooltip = obj->explicit_tooltip;
389 id = data->id;
390 if (!id || !*id) { /* no external id, so use the internal one */
391 agxbinit(&xb, SMALLBUF, buf);
392 if (!env->objid) {
393 env->objid = strdup(getObjId(job, obj->u.n, &xb));
394 env->objid_set = 1;
395 }
396 agxbput(&xb, env->objid);
397 sprintf(intbuf, "_%d", anchorId++);
398 agxbput(&xb, intbuf);
399 id = agxbuse(&xb);
400 internalId = 1;
401 }
402 changed =
403 initMapData(job, NULL, data->href, data->title, data->target, id,
404 obj->u.g);
405 if (internalId)
406 agxbfree(&xb);
407
408 if (changed) {
409 if (obj->url || obj->explicit_tooltip) {
410 emit_map_rect(job, b);
411 gvrender_begin_anchor(job,
412 obj->url, obj->tooltip, obj->target,
413 obj->id);
414 }
415 }
416 return changed;
417}
418
419#define RESET(fld) \
420 if(obj->fld != save->fld) {free(obj->fld); obj->fld = save->fld;}
421
422/* endAnchor:
423 * Pop context pushed by initAnchor.
424 * This is done by ending current anchor, restoring old values and
425 * freeing new.
426 *
427 * NB: We don't save or restore geometric map info. This is because
428 * this preservation of map context is only necessary for SVG-like
429 * systems where graphical items are wrapped in an anchor, and we map
430 * top-down. For ordinary map anchors, this is all done bottom-up, so
431 * the geometric map info at the higher level hasn't been emitted yet.
432 */
433static void endAnchor(GVJ_t * job, htmlmap_data_t * save)
434{
435 obj_state_t *obj = job->obj;
436
437 if (obj->url || obj->explicit_tooltip)
438 gvrender_end_anchor(job);
439 RESET(url);
440 RESET(tooltip);
441 RESET(target);
442 RESET(id);
443 obj->explicit_tooltip = save->explicit_tooltip;
444}
445
446/* forward declaration */
447static void emit_html_cell(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env);
448
449/* emit_html_rules:
450 * place vertical and horizontal lines between adjacent cells and
451 * extend the lines to intersect the rounded table boundary
452 */
453static void
454emit_html_rules(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env, char *color, htmlcell_t* nextc)
455{
456 pointf rule_pt;
457 double rule_length;
458 unsigned char base;
459 boxf pts = cp->data.box;
460 pointf pos = env->pos;
461
462 if (!color)
463 color = DEFAULT_COLOR;
464 gvrender_set_fillcolor(job, color);
465 gvrender_set_pencolor(job, color);
466
467 pts = cp->data.box;
468 pts.LL.x += pos.x;
469 pts.UR.x += pos.x;
470 pts.LL.y += pos.y;
471 pts.UR.y += pos.y;
472
473 //Determine vertical line coordinate and length
474 if ((cp->ruled & HTML_VRULE) && (cp->col + cp->cspan < cp->parent->cc)) {
475 if (cp->row == 0) { // first row
476 // extend to center of table border and add half cell spacing
477 base = cp->parent->data.border + cp->parent->data.space / 2;
478 rule_pt.y = pts.LL.y - cp->parent->data.space / 2;
479 } else if (cp->row + cp->rspan == cp->parent->rc) { // bottom row
480 // extend to center of table border and add half cell spacing
481 base = cp->parent->data.border + cp->parent->data.space / 2;
482 rule_pt.y = pts.LL.y - cp->parent->data.space / 2 - base;
483 } else {
484 base = 0;
485 rule_pt.y = pts.LL.y - cp->parent->data.space / 2;
486 }
487 rule_pt.x = pts.UR.x + cp->parent->data.space / 2;
488 rule_length = base + pts.UR.y - pts.LL.y + cp->parent->data.space;
489 doSide(job, rule_pt, 0, rule_length);
490 }
491 //Determine the horizontal coordinate and length
492 if ((cp->ruled & HTML_HRULE) && (cp->row + cp->rspan < cp->parent->rc)) {
493 if (cp->col == 0) { // first column
494 // extend to center of table border and add half cell spacing
495 base = cp->parent->data.border + cp->parent->data.space / 2;
496 rule_pt.x = pts.LL.x - base - cp->parent->data.space / 2;
497 if (cp->col + cp->cspan == cp->parent->cc) // also last column
498 base *= 2;
499 /* incomplete row of cells; extend line to end */
500 else if (nextc && (nextc->row != cp->row)) {
501 base += (cp->parent->data.box.UR.x + pos.x) - (pts.UR.x + cp->parent->data.space / 2);
502 }
503 } else if (cp->col + cp->cspan == cp->parent->cc) { // last column
504 // extend to center of table border and add half cell spacing
505 base = cp->parent->data.border + cp->parent->data.space / 2;
506 rule_pt.x = pts.LL.x - cp->parent->data.space / 2;
507 } else {
508 base = 0;
509 rule_pt.x = pts.LL.x - cp->parent->data.space / 2;
510 /* incomplete row of cells; extend line to end */
511 if (nextc && (nextc->row != cp->row)) {
512 base += (cp->parent->data.box.UR.x + pos.x) - (pts.UR.x + cp->parent->data.space / 2);
513 }
514 }
515 rule_pt.y = pts.LL.y - cp->parent->data.space / 2;
516 rule_length = base + pts.UR.x - pts.LL.x + cp->parent->data.space;
517 doSide(job, rule_pt, rule_length, 0);
518 }
519}
520
521static void emit_html_tbl(GVJ_t * job, htmltbl_t * tbl, htmlenv_t * env)
522{
523 boxf pts = tbl->data.box;
524 pointf pos = env->pos;
525 htmlcell_t **cells = tbl->u.n.cells;
526 htmlcell_t *cp;
527 static textfont_t savef;
528 htmlmap_data_t saved;
529 int anchor; /* if true, we need to undo anchor settings. */
530 int doAnchor = (tbl->data.href || tbl->data.target);
531 pointf AF[4];
532
533 if (tbl->font)
534 pushFontInfo(env, tbl->font, &savef);
535
536 pts.LL.x += pos.x;
537 pts.UR.x += pos.x;
538 pts.LL.y += pos.y;
539 pts.UR.y += pos.y;
540
541 if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST))
542 anchor = initAnchor(job, env, &tbl->data, pts, &saved);
543 else
544 anchor = 0;
545
546 if (!(tbl->data.style & INVISIBLE)) {
547
548 /* Fill first */
549 if (tbl->data.bgcolor) {
550 char *clrs[2];
551 int filled =
552 setFill(job, tbl->data.bgcolor, tbl->data.gradientangle,
553 tbl->data.style, clrs);
554 if (tbl->data.style & ROUNDED) {
555 round_corners(job, mkPts(AF, pts, tbl->data.border), 4,
556 ROUNDED, filled);
557 } else
558 gvrender_box(job, pts, filled);
559 free(clrs[0]);
560 }
561
562 while (*cells) {
563 emit_html_cell(job, *cells, env);
564 cells++;
565 }
566
567 /* Draw table rules and border.
568 * Draw after cells so we can draw over any fill.
569 * At present, we set the penwidth to 1 for rules until we provide the calculations to take
570 * into account wider rules.
571 */
572 cells = tbl->u.n.cells;
573 gvrender_set_penwidth(job, 1.0);
574 while ((cp = *cells++)) {
575 if (cp->ruled)
576 emit_html_rules(job, cp, env, tbl->data.pencolor, *cells);
577 }
578
579 if (tbl->data.border)
580 doBorder(job, &tbl->data, pts);
581
582 }
583
584 if (anchor)
585 endAnchor(job, &saved);
586
587 if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) {
588 if (initAnchor(job, env, &tbl->data, pts, &saved))
589 endAnchor(job, &saved);
590 }
591
592 if (tbl->font)
593 popFontInfo(env, &savef);
594}
595
596/* emit_html_img:
597 * The image will be centered in the given box.
598 * Scaling is determined by either the image's scale attribute,
599 * or the imagescale attribute of the graph object being drawn.
600 */
601static void emit_html_img(GVJ_t * job, htmlimg_t * cp, htmlenv_t * env)
602{
603 pointf A[4];
604 boxf bb = cp->box;
605 char *scale;
606
607 bb.LL.x += env->pos.x;
608 bb.LL.y += env->pos.y;
609 bb.UR.x += env->pos.x;
610 bb.UR.y += env->pos.y;
611
612 A[0] = bb.UR;
613 A[2] = bb.LL;
614 A[1].x = A[2].x;
615 A[1].y = A[0].y;
616 A[3].x = A[0].x;
617 A[3].y = A[2].y;
618
619 if (cp->scale)
620 scale = cp->scale;
621 else
622 scale = env->imgscale;
623 assert(cp->src);
624 assert(cp->src[0]);
625 gvrender_usershape(job, cp->src, A, 4, TRUE, scale, "mc");
626}
627
628static void emit_html_cell(GVJ_t * job, htmlcell_t * cp, htmlenv_t * env)
629{
630 htmlmap_data_t saved;
631 boxf pts = cp->data.box;
632 pointf pos = env->pos;
633 int inAnchor, doAnchor = (cp->data.href || cp->data.target);
634 pointf AF[4];
635
636 pts.LL.x += pos.x;
637 pts.UR.x += pos.x;
638 pts.LL.y += pos.y;
639 pts.UR.y += pos.y;
640
641 if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST))
642 inAnchor = initAnchor(job, env, &cp->data, pts, &saved);
643 else
644 inAnchor = 0;
645
646 if (!(cp->data.style & INVISIBLE)) {
647 if (cp->data.bgcolor) {
648 char *clrs[2];
649 int filled =
650 setFill(job, cp->data.bgcolor, cp->data.gradientangle,
651 cp->data.style, clrs);
652 if (cp->data.style & ROUNDED) {
653 round_corners(job, mkPts(AF, pts, cp->data.border), 4,
654 ROUNDED, filled);
655 } else
656 gvrender_box(job, pts, filled);
657 free(clrs[0]);
658 }
659
660 if (cp->data.border)
661 doBorder(job, &cp->data, pts);
662
663 if (cp->child.kind == HTML_TBL)
664 emit_html_tbl(job, cp->child.u.tbl, env);
665 else if (cp->child.kind == HTML_IMAGE)
666 emit_html_img(job, cp->child.u.img, env);
667 else
668 emit_html_txt(job, cp->child.u.txt, env);
669 }
670
671 if (inAnchor)
672 endAnchor(job, &saved);
673
674 if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) {
675 if (initAnchor(job, env, &cp->data, pts, &saved))
676 endAnchor(job, &saved);
677 }
678}
679
680/* allocObj:
681 * Push new obj on stack to be used in common by all
682 * html elements with anchors.
683 * This inherits the type, emit_state, and object of the
684 * parent, as well as the url, explicit, target and tooltip.
685 */
686static void allocObj(GVJ_t * job)
687{
688 obj_state_t *obj;
689 obj_state_t *parent;
690
691 obj = push_obj_state(job);
692 parent = obj->parent;
693 obj->type = parent->type;
694 obj->emit_state = parent->emit_state;
695 switch (obj->type) {
696 case NODE_OBJTYPE:
697 obj->u.n = parent->u.n;
698 break;
699 case ROOTGRAPH_OBJTYPE:
700 obj->u.g = parent->u.g;
701 break;
702 case CLUSTER_OBJTYPE:
703 obj->u.sg = parent->u.sg;
704 break;
705 case EDGE_OBJTYPE:
706 obj->u.e = parent->u.e;
707 break;
708 }
709 obj->url = parent->url;
710 obj->tooltip = parent->tooltip;
711 obj->target = parent->target;
712 obj->explicit_tooltip = parent->explicit_tooltip;
713}
714
715static void freeObj(GVJ_t * job)
716{
717 obj_state_t *obj = job->obj;
718
719 obj->url = NULL;
720 obj->tooltip = NULL;
721 obj->target = NULL;
722 obj->id = NULL;
723 pop_obj_state(job);
724}
725
726static double
727heightOfLbl (htmllabel_t * lp)
728{
729 double sz = 0.0;
730
731 switch (lp->kind) {
732 case HTML_TBL:
733 sz = lp->u.tbl->data.box.UR.y - lp->u.tbl->data.box.LL.y;
734 break;
735 case HTML_IMAGE:
736 sz = lp->u.img->box.UR.y - lp->u.img->box.LL.y;
737 break;
738 case HTML_TEXT:
739 sz = lp->u.txt->box.UR.y - lp->u.txt->box.LL.y;
740 break;
741 }
742 return sz;
743}
744
745/* emit_html_label:
746 */
747void emit_html_label(GVJ_t * job, htmllabel_t * lp, textlabel_t * tp)
748{
749 htmlenv_t env;
750 pointf p;
751
752 allocObj(job);
753
754 p = tp->pos;
755 switch (tp->valign) {
756 case 't':
757 p.y = tp->pos.y + (tp->space.y - heightOfLbl(lp))/ 2.0 - 1;
758 break;
759 case 'b':
760 p.y = tp->pos.y - (tp->space.y - heightOfLbl(lp))/ 2.0 - 1;
761 break;
762 default:
763 /* no-op */
764 break;
765 }
766 env.pos = p;
767 env.finfo.color = tp->fontcolor;
768 env.finfo.name = tp->fontname;
769 env.finfo.size = tp->fontsize;
770 env.imgscale = agget(job->obj->u.n, "imagescale");
771 env.objid = job->obj->id;
772 env.objid_set = 0;
773 if ((env.imgscale == NULL) || (env.imgscale[0] == '\0'))
774 env.imgscale = "false";
775 if (lp->kind == HTML_TBL) {
776 htmltbl_t *tbl = lp->u.tbl;
777
778 /* set basic graphics context */
779 /* Need to override line style set by node. */
780 gvrender_set_style(job, job->gvc->defaultlinestyle);
781 if (tbl->data.pencolor)
782 gvrender_set_pencolor(job, tbl->data.pencolor);
783 else
784 gvrender_set_pencolor(job, DEFAULT_COLOR);
785 emit_html_tbl(job, tbl, &env);
786 } else {
787 emit_html_txt(job, lp->u.txt, &env);
788 }
789 if (env.objid_set)
790 free(env.objid);
791 freeObj(job);
792}
793
794void free_html_data(htmldata_t * dp)
795{
796 free(dp->href);
797 free(dp->port);
798 free(dp->target);
799 free(dp->id);
800 free(dp->title);
801 free(dp->bgcolor);
802 free(dp->pencolor);
803}
804
805void free_html_text(htmltxt_t * t)
806{
807 htextspan_t *tl;
808 textspan_t *ti;
809 int i, j;
810
811 if (!t)
812 return;
813
814 tl = t->spans;
815 for (i = 0; i < t->nspans; i++) {
816 ti = tl->items;
817 for (j = 0; j < tl->nitems; j++) {
818 if (ti->str)
819 free(ti->str);
820 if (ti->layout && ti->free_layout)
821 ti->free_layout(ti->layout);
822 ti++;
823 }
824 tl++;
825 }
826 if (t->spans)
827 free(t->spans);
828 free(t);
829}
830
831void free_html_img(htmlimg_t * ip)
832{
833 free(ip->src);
834 free(ip);
835}
836
837static void free_html_cell(htmlcell_t * cp)
838{
839 free_html_label(&cp->child, 0);
840 free_html_data(&cp->data);
841 free(cp);
842}
843
844/* free_html_tbl:
845 * If tbl->n_rows is negative, table is in initial state from
846 * HTML parse, with data stored in u.p. Once run through processTbl,
847 * data is stored in u.n and tbl->n_rows is > 0.
848 */
849static void free_html_tbl(htmltbl_t * tbl)
850{
851 htmlcell_t **cells;
852
853 if (tbl->rc == -1) {
854 dtclose(tbl->u.p.rows);
855 } else {
856 cells = tbl->u.n.cells;
857
858 free(tbl->heights);
859 free(tbl->widths);
860 while (*cells) {
861 free_html_cell(*cells);
862 cells++;
863 }
864 free(tbl->u.n.cells);
865 }
866 free_html_data(&tbl->data);
867 free(tbl);
868}
869
870void free_html_label(htmllabel_t * lp, int root)
871{
872 if (lp->kind == HTML_TBL)
873 free_html_tbl(lp->u.tbl);
874 else if (lp->kind == HTML_IMAGE)
875 free_html_img(lp->u.img);
876 else
877 free_html_text(lp->u.txt);
878 if (root)
879 free(lp);
880}
881
882static htmldata_t *portToTbl(htmltbl_t *, char *); /* forward declaration */
883
884static htmldata_t *portToCell(htmlcell_t * cp, char *id)
885{
886 htmldata_t *rv;
887
888 if (cp->data.port && (strcasecmp(cp->data.port, id) == 0))
889 rv = &cp->data;
890 else if (cp->child.kind == HTML_TBL)
891 rv = portToTbl(cp->child.u.tbl, id);
892 else
893 rv = NULL;
894
895 return rv;
896}
897
898/* portToTbl:
899 * See if tp or any of its child cells has the given port id.
900 * If true, return corresponding box.
901 */
902static htmldata_t *portToTbl(htmltbl_t * tp, char *id)
903{
904 htmldata_t *rv;
905 htmlcell_t **cells;
906 htmlcell_t *cp;
907
908 if (tp->data.port && (strcasecmp(tp->data.port, id) == 0))
909 rv = &tp->data;
910 else {
911 rv = NULL;
912 cells = tp->u.n.cells;
913 while ((cp = *cells++)) {
914 if ((rv = portToCell(cp, id)))
915 break;
916 }
917 }
918
919 return rv;
920}
921
922/* html_port:
923 * See if edge port corresponds to part of the html node.
924 * Assume pname != "".
925 * If successful, return pointer to port's box.
926 * Else return NULL.
927 */
928boxf *html_port(node_t * n, char *pname, int *sides)
929{
930 htmldata_t *tp;
931 htmllabel_t *lbl = ND_label(n)->u.html;
932 boxf *rv = NULL;
933
934 if (lbl->kind == HTML_TEXT)
935 return NULL;
936
937 tp = portToTbl(lbl->u.tbl, pname);
938 if (tp) {
939 rv = &tp->box;
940 *sides = tp->sides;
941 }
942 return rv;
943
944}
945
946/* html_path:
947 * Return a box in a table containing the given endpoint.
948 * If the top flow is text (no internal structure), return
949 * the box of the flow
950 * Else return the box of the subtable containing the point.
951 * Because of spacing, the point might not be in any subtable.
952 * In that case, return the top flow's box.
953 * Note that box[0] must contain the edge point. Additional boxes
954 * move out to the boundary.
955 *
956 * At present, unimplemented, since the label may be inside a
957 * non-box node and we need to figure out what this means.
958 */
959int html_path(node_t * n, port * p, int side, boxf * rv, int *k)
960{
961#ifdef UNIMPL
962 point p;
963 tbl_t *info;
964 tbl_t *t;
965 boxf b;
966 int i;
967
968 info = (tbl_t *) ND_shape_info(n);
969 assert(info->tbls);
970 info = info->tbls[0]; /* top-level flow */
971 assert(IS_FLOW(info));
972
973 b = info->box;
974 if (info->tbl) {
975 info = info->tbl;
976 if (pt == 1)
977 p = ED_tail_port(e).p;
978 else
979 p = ED_head_port(e).p;
980 p = flip_pt(p, GD_rankdir(n->graph)); /* move p to node's coordinate system */
981 for (i = 0; (t = info->tbls[i]) != 0; i++)
982 if (INSIDE(p, t->box)) {
983 b = t->box;
984 break;
985 }
986 }
987
988 /* move box into layout coordinate system */
989 if (GD_flip(n->graph))
990 b = flip_trans_box(b, ND_coord_i(n));
991 else
992 b = move_box(b, ND_coord_i(n));
993
994 *k = 1;
995 *rv = b;
996 if (pt == 1)
997 return BOTTOM;
998 else
999 return TOP;
1000#endif
1001 return 0;
1002}
1003
1004static int size_html_txt(GVC_t *gvc, htmltxt_t * ftxt, htmlenv_t * env)
1005{
1006 double xsize = 0.0; /* width of text block */
1007 double ysize = 0.0; /* height of text block */
1008 double lsize; /* height of current line */
1009 double mxfsize = 0.0; /* max. font size for the current line */
1010 double curbline = 0.0; /* dist. of current base line from top */
1011 pointf sz;
1012 int i, j;
1013 double width;
1014 textspan_t lp;
1015 textfont_t tf = {NULL,NULL,NULL,0.0,0,0};
1016 double maxoffset, mxysize;
1017 int simple = 1; /* one item per span, same font size/face, no flags */
1018 double prev_fsize = -1;
1019 char* prev_fname = NULL;
1020
1021 for (i = 0; i < ftxt->nspans; i++) {
1022 if (ftxt->spans[i].nitems > 1) {
1023 simple = 0;
1024 break;
1025 }
1026 if (ftxt->spans[i].items[0].font) {
1027 if (ftxt->spans[i].items[0].font->flags) {
1028 simple = 0;
1029 break;
1030 }
1031 if (ftxt->spans[i].items[0].font->size > 0)
1032 tf.size = ftxt->spans[i].items[0].font->size;
1033 else
1034 tf.size = env->finfo.size;
1035 if (ftxt->spans[i].items[0].font->name)
1036 tf.name = ftxt->spans[i].items[0].font->name;
1037 else
1038 tf.name = env->finfo.name;
1039 }
1040 else {
1041 tf.size = env->finfo.size;
1042 tf.name = env->finfo.name;
1043 }
1044 if (prev_fsize == -1)
1045 prev_fsize = tf.size;
1046 else if (tf.size != prev_fsize) {
1047 simple = 0;
1048 break;
1049 }
1050 if (prev_fname == NULL)
1051 prev_fname = tf.name;
1052 else if (strcmp(tf.name,prev_fname)) {
1053 simple = 0;
1054 break;
1055 }
1056 }
1057 ftxt->simple = simple;
1058
1059 for (i = 0; i < ftxt->nspans; i++) {
1060 width = 0;
1061 mxysize = maxoffset = mxfsize = 0;
1062 for (j = 0; j < ftxt->spans[i].nitems; j++) {
1063 lp.str =
1064 strdup_and_subst_obj(ftxt->spans[i].items[j].str,
1065 env->obj);
1066 if (ftxt->spans[i].items[j].font) {
1067 if (ftxt->spans[i].items[j].font->flags)
1068 tf.flags = ftxt->spans[i].items[j].font->flags;
1069 else if (env->finfo.flags > 0)
1070 tf.flags = env->finfo.flags;
1071 else
1072 tf.flags = 0;
1073 if (ftxt->spans[i].items[j].font->size > 0)
1074 tf.size = ftxt->spans[i].items[j].font->size;
1075 else
1076 tf.size = env->finfo.size;
1077 if (ftxt->spans[i].items[j].font->name)
1078 tf.name = ftxt->spans[i].items[j].font->name;
1079 else
1080 tf.name = env->finfo.name;
1081 if (ftxt->spans[i].items[j].font->color)
1082 tf.color = ftxt->spans[i].items[j].font->color;
1083 else
1084 tf.color = env->finfo.color;
1085 } else {
1086 tf.size = env->finfo.size;
1087 tf.name = env->finfo.name;
1088 tf.color = env->finfo.color;
1089 tf.flags = env->finfo.flags;
1090 }
1091 lp.font = dtinsert(gvc->textfont_dt, &tf);
1092 sz = textspan_size(gvc, &lp);
1093 free(ftxt->spans[i].items[j].str);
1094 ftxt->spans[i].items[j].str = lp.str;
1095 ftxt->spans[i].items[j].size.x = sz.x;
1096 ftxt->spans[i].items[j].yoffset_layout = lp.yoffset_layout;
1097 ftxt->spans[i].items[j].yoffset_centerline = lp.yoffset_centerline;
1098 ftxt->spans[i].items[j].font = lp.font;
1099 ftxt->spans[i].items[j].layout = lp.layout;
1100 ftxt->spans[i].items[j].free_layout = lp.free_layout;
1101 width += sz.x;
1102 mxfsize = MAX(tf.size, mxfsize);
1103 mxysize = MAX(sz.y, mxysize);
1104 maxoffset = MAX(lp.yoffset_centerline, maxoffset);
1105 }
1106 /* lsize = mxfsize * LINESPACING; */
1107 ftxt->spans[i].size = width;
1108 /* ysize - curbline is the distance from the previous
1109 * baseline to the bottom of the previous line.
1110 * Then, in the current line, we set the baseline to
1111 * be 5/6 of the max. font size. Thus, lfsize gives the
1112 * distance from the previous baseline to the new one.
1113 */
1114 /* ftxt->spans[i].lfsize = 5*mxfsize/6 + ysize - curbline; */
1115 if (simple) {
1116 lsize = mxysize;
1117 if (i == 0)
1118 ftxt->spans[i].lfsize = mxfsize;
1119 else
1120 ftxt->spans[i].lfsize = mxysize;
1121 }
1122 else {
1123 lsize = mxfsize;
1124 if (i == 0)
1125 ftxt->spans[i].lfsize = mxfsize - maxoffset;
1126 else
1127 ftxt->spans[i].lfsize = mxfsize + ysize - curbline - maxoffset;
1128 }
1129 curbline += ftxt->spans[i].lfsize;
1130 xsize = MAX(width, xsize);
1131 ysize += lsize;
1132 }
1133 ftxt->box.UR.x = xsize;
1134 if (ftxt->nspans == 1)
1135 ftxt->box.UR.y = mxysize;
1136 else
1137 ftxt->box.UR.y = ysize;
1138 return 0;
1139}
1140
1141/* forward declarion for recursive usage */
1142static int size_html_tbl(graph_t * g, htmltbl_t * tbl, htmlcell_t * parent,
1143 htmlenv_t * env);
1144
1145/* size_html_img:
1146 */
1147static int size_html_img(htmlimg_t * img, htmlenv_t * env)
1148{
1149 box b;
1150 int rv;
1151
1152 b.LL.x = b.LL.y = 0;
1153 b.UR = gvusershape_size(env->g, img->src);
1154 if ((b.UR.x == -1) && (b.UR.y == -1)) {
1155 rv = 1;
1156 b.UR.x = b.UR.y = 0;
1157 agerr(AGERR, "No or improper image file=\"%s\"\n", img->src);
1158 } else {
1159 rv = 0;
1160 GD_has_images(env->g) = TRUE;
1161 }
1162
1163 B2BF(b, img->box);
1164 return rv;
1165}
1166
1167/* size_html_cell:
1168 */
1169static int
1170size_html_cell(graph_t * g, htmlcell_t * cp, htmltbl_t * parent,
1171 htmlenv_t * env)
1172{
1173 int rv;
1174 pointf sz, child_sz;
1175 int margin;
1176
1177 cp->parent = parent;
1178 if (!(cp->data.flags & PAD_SET)) {
1179 if (parent->data.flags & PAD_SET)
1180 cp->data.pad = parent->data.pad;
1181 else
1182 cp->data.pad = DEFAULT_CELLPADDING;
1183 }
1184 if (!(cp->data.flags & BORDER_SET)) {
1185 if (parent->cb >= 0)
1186 cp->data.border = parent->cb;
1187 else if (parent->data.flags & BORDER_SET)
1188 cp->data.border = parent->data.border;
1189 else
1190 cp->data.border = DEFAULT_BORDER;
1191 }
1192
1193 if (cp->child.kind == HTML_TBL) {
1194 rv = size_html_tbl(g, cp->child.u.tbl, cp, env);
1195 child_sz = cp->child.u.tbl->data.box.UR;
1196 } else if (cp->child.kind == HTML_IMAGE) {
1197 rv = size_html_img(cp->child.u.img, env);
1198 child_sz = cp->child.u.img->box.UR;
1199 } else {
1200 rv = size_html_txt(GD_gvc(g), cp->child.u.txt, env);
1201 child_sz = cp->child.u.txt->box.UR;
1202 }
1203
1204 margin = 2 * (cp->data.pad + cp->data.border);
1205 sz.x = child_sz.x + margin;
1206 sz.y = child_sz.y + margin;
1207
1208 if (cp->data.flags & FIXED_FLAG) {
1209 if (cp->data.width && cp->data.height) {
1210 if (((cp->data.width < sz.x) || (cp->data.height < sz.y)) && (cp->child.kind != HTML_IMAGE)) {
1211 agerr(AGWARN, "cell size too small for content\n");
1212 rv = 1;
1213 }
1214 sz.x = sz.y = 0;
1215
1216 } else {
1217 agerr(AGWARN,
1218 "fixed cell size with unspecified width or height\n");
1219 rv = 1;
1220 }
1221 }
1222 cp->data.box.UR.x = MAX(sz.x, cp->data.width);
1223 cp->data.box.UR.y = MAX(sz.y, cp->data.height);
1224 return rv;
1225}
1226
1227static int findCol(PointSet * ps, int row, int col, htmlcell_t * cellp)
1228{
1229 int notFound = 1;
1230 int lastc;
1231 int i, j, c;
1232 int end = cellp->cspan - 1;
1233
1234 while (notFound) {
1235 lastc = col + end;
1236 for (c = lastc; c >= col; c--) {
1237 if (isInPS(ps, c, row))
1238 break;
1239 }
1240 if (c >= col) /* conflict : try column after */
1241 col = c + 1;
1242 else
1243 notFound = 0;
1244 }
1245 for (j = col; j < col + cellp->cspan; j++) {
1246 for (i = row; i < row + cellp->rspan; i++) {
1247 addPS(ps, j, i);
1248 }
1249 }
1250 return col;
1251}
1252
1253/* processTbl:
1254 * Convert parser representation of cells into final form.
1255 * Find column and row positions of cells.
1256 * Recursively size cells.
1257 * Return 1 if problem sizing a cell.
1258 */
1259static int processTbl(graph_t * g, htmltbl_t * tbl, htmlenv_t * env)
1260{
1261 pitem *rp;
1262 pitem *cp;
1263 Dt_t *cdict;
1264 int r, c;
1265 htmlcell_t *cellp;
1266 htmlcell_t **cells;
1267 Dt_t *rows = tbl->u.p.rows;
1268 int rv = 0;
1269 int n_rows = 0;
1270 int n_cols = 0;
1271 PointSet *ps = newPS();
1272 Dt_t *is = openIntSet();
1273
1274 rp = (pitem *) dtflatten(rows);
1275 size_t cnt = 0;
1276 r = 0;
1277 while (rp) {
1278 cdict = rp->u.rp;
1279 cp = (pitem *) dtflatten(cdict);
1280 while (cp) {
1281 cellp = cp->u.cp;
1282 cnt++;
1283 cp = (pitem *) dtlink(cdict, (Dtlink_t *) cp);
1284 }
1285 if (rp->ruled) {
1286 addIntSet(is, r + 1);
1287 }
1288 rp = (pitem *) dtlink(rows, (Dtlink_t *) rp);
1289 r++;
1290 }
1291
1292 cells = tbl->u.n.cells = N_NEW(cnt + 1, htmlcell_t *);
1293 rp = (pitem *) dtflatten(rows);
1294 r = 0;
1295 while (rp) {
1296 cdict = rp->u.rp;
1297 cp = (pitem *) dtflatten(cdict);
1298 c = 0;
1299 while (cp) {
1300 cellp = cp->u.cp;
1301 *cells++ = cellp;
1302 rv |= size_html_cell(g, cellp, tbl, env);
1303 c = findCol(ps, r, c, cellp);
1304 cellp->row = r;
1305 cellp->col = c;
1306 c += cellp->cspan;
1307 n_cols = MAX(c, n_cols);
1308 n_rows = MAX(r + cellp->rspan, n_rows);
1309 if (inIntSet(is, r + cellp->rspan))
1310 cellp->ruled |= HTML_HRULE;
1311 cp = (pitem *) dtlink(cdict, (Dtlink_t *) cp);
1312 }
1313 rp = (pitem *) dtlink(rows, (Dtlink_t *) rp);
1314 r++;
1315 }
1316 tbl->rc = n_rows;
1317 tbl->cc = n_cols;
1318 dtclose(rows);
1319 dtclose(is);
1320 freePS(ps);
1321 return rv;
1322}
1323
1324/* Split size x over n pieces with spacing s.
1325 * We subtract s*(n-1) from x, divide by n and
1326 * take the ceiling.
1327 */
1328#define SPLIT(x,n,s) (((x) - ((s)-1)*((n)-1)) / (n))
1329
1330/* sizeLinearArray:
1331 * Determine sizes of rows and columns. The size of a column is the
1332 * maximum width of any cell in it. Similarly for rows.
1333 * A cell spanning columns contributes proportionately to each column
1334 * it is in.
1335 */
1336void sizeLinearArray(htmltbl_t * tbl)
1337{
1338 htmlcell_t *cp;
1339 htmlcell_t **cells;
1340 int wd, ht, i, x, y;
1341
1342 tbl->heights = N_NEW(tbl->rc + 1, int);
1343 tbl->widths = N_NEW(tbl->cc + 1, int);
1344
1345 for (cells = tbl->u.n.cells; *cells; cells++) {
1346 cp = *cells;
1347 if (cp->rspan == 1)
1348 ht = cp->data.box.UR.y;
1349 else {
1350 ht = SPLIT(cp->data.box.UR.y, cp->rspan, tbl->data.space);
1351 ht = MAX(ht, 1);
1352 }
1353 if (cp->cspan == 1)
1354 wd = cp->data.box.UR.x;
1355 else {
1356 wd = SPLIT(cp->data.box.UR.x, cp->cspan, tbl->data.space);
1357 wd = MAX(wd, 1);
1358 }
1359 for (i = cp->row; i < cp->row + cp->rspan; i++) {
1360 y = tbl->heights[i];
1361 tbl->heights[i] = MAX(y, ht);
1362 }
1363 for (i = cp->col; i < cp->col + cp->cspan; i++) {
1364 x = tbl->widths[i];
1365 tbl->widths[i] = MAX(x, wd);
1366 }
1367 }
1368}
1369
1370static char *nnames[] = {
1371 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
1372 "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
1373};
1374
1375/* nToName:
1376 * Convert int to its decimal string representation.
1377 */
1378char *nToName(int c)
1379{
1380 static char name[100];
1381
1382 if (c < sizeof(nnames) / sizeof(char *))
1383 return nnames[c];
1384
1385 sprintf(name, "%d", c);
1386 return name;
1387}
1388
1389/* closeGraphs:
1390 * Clean up graphs made for setting column and row widths.
1391 */
1392static void closeGraphs(graph_t * rowg, graph_t * colg)
1393{
1394 node_t *n;
1395 for (n = GD_nlist(colg); n; n = ND_next(n)) {
1396 free_list(ND_in(n));
1397 free_list(ND_out(n));
1398 }
1399
1400 agclose(rowg);
1401 agclose(colg);
1402}
1403
1404/* checkChain:
1405 * For each pair of nodes in the node list, add an edge if none exists.
1406 * Assumes node list has nodes ordered correctly.
1407 */
1408static void checkChain(graph_t * g)
1409{
1410 node_t *t;
1411 node_t *h;
1412 edge_t *e;
1413 t = GD_nlist(g);
1414 for (h = ND_next(t); h; h = ND_next(h)) {
1415 if (!agfindedge(g, t, h)) {
1416 e = agedge(g, t, h, NULL, 1);
1417 agbindrec(e, "Agedgeinfo_t", sizeof(Agedgeinfo_t), TRUE);
1418 ED_minlen(e) = 0;
1419 elist_append(e, ND_out(t));
1420 elist_append(e, ND_in(h));
1421 }
1422 t = h;
1423 }
1424}
1425
1426/* checkEdge:
1427 * Check for edge in g. If it exists, set its minlen to max of sz and
1428 * current minlen. Else, create it and set minlen to sz.
1429 */
1430static void
1431checkEdge (graph_t* g, node_t* t, node_t* h, int sz)
1432{
1433 edge_t* e;
1434
1435 e = agfindedge (g, t, h);
1436 if (e)
1437 ED_minlen(e) = MAX(ED_minlen(e), sz);
1438 else {
1439 e = agedge(g, t, h, NULL, 1);
1440 agbindrec(e, "Agedgeinfo_t", sizeof(Agedgeinfo_t), TRUE);
1441 ED_minlen(e) = sz;
1442 elist_append(e, ND_out(t));
1443 elist_append(e, ND_in(h));
1444 }
1445}
1446
1447/* makeGraphs:
1448 * Generate dags modeling the row and column constraints.
1449 * If the table has cc columns, we create the graph
1450 * 0 -> 1 -> 2 -> ... -> cc
1451 * and if a cell starts in column c with span cspan, with
1452 * width w, we add the edge c -> c+cspan [minlen = w].
1453 * Ditto for rows.
1454 *
1455 */
1456void makeGraphs(htmltbl_t * tbl, graph_t * rowg, graph_t * colg)
1457{
1458 htmlcell_t *cp;
1459 htmlcell_t **cells;
1460 node_t *t;
1461 node_t *lastn;
1462 node_t *h;
1463 int i;
1464
1465 lastn = NULL;
1466 for (i = 0; i <= tbl->cc; i++) {
1467 t = agnode(colg, nToName(i), 1);
1468 agbindrec(t, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE);
1469 alloc_elist(tbl->rc, ND_in(t));
1470 alloc_elist(tbl->rc, ND_out(t));
1471 if (lastn) {
1472 ND_next(lastn) = t;
1473 lastn = t;
1474 } else {
1475 lastn = GD_nlist(colg) = t;
1476 }
1477 }
1478 lastn = NULL;
1479 for (i = 0; i <= tbl->rc; i++) {
1480 t = agnode(rowg, nToName(i), 1);
1481 agbindrec(t, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE);
1482 alloc_elist(tbl->cc, ND_in(t));
1483 alloc_elist(tbl->cc, ND_out(t));
1484 if (lastn) {
1485 ND_next(lastn) = t;
1486 lastn = t;
1487 } else {
1488 lastn = GD_nlist(rowg) = t;
1489 }
1490 }
1491
1492 for (cells = tbl->u.n.cells; *cells; cells++) {
1493 cp = *cells;
1494 t = agfindnode(colg, nToName(cp->col));
1495 h = agfindnode(colg, nToName(cp->col + cp->cspan));
1496 checkEdge (colg, t, h, cp->data.box.UR.x);
1497
1498 t = agfindnode(rowg, nToName(cp->row));
1499 h = agfindnode(rowg, nToName(cp->row + cp->rspan));
1500 checkEdge (rowg, t, h, cp->data.box.UR.y);
1501 }
1502
1503 /* Make sure that 0 <= 1 <= 2 ...k. This implies graph connected. */
1504 checkChain(colg);
1505 checkChain(rowg);
1506}
1507
1508/* setSizes:
1509 * Use rankings to determine cell dimensions. The rank values
1510 * give the coordinate, so to get the width/height, we have
1511 * to subtract the previous value.
1512 */
1513void setSizes(htmltbl_t * tbl, graph_t * rowg, graph_t * colg)
1514{
1515 int i;
1516 node_t *n;
1517 int prev;
1518
1519 prev = 0;
1520 n = GD_nlist(rowg);
1521 for (i = 0, n = ND_next(n); n; i++, n = ND_next(n)) {
1522 tbl->heights[i] = ND_rank(n) - prev;
1523 prev = ND_rank(n);
1524 }
1525 prev = 0;
1526 n = GD_nlist(colg);
1527 for (i = 0, n = ND_next(n); n; i++, n = ND_next(n)) {
1528 tbl->widths[i] = ND_rank(n) - prev;
1529 prev = ND_rank(n);
1530 }
1531
1532}
1533
1534/* sizeArray:
1535 * Set column and row sizes. Optimize for minimum width and
1536 * height. Where there is slack, try to distribute evenly.
1537 * We do this by encoding cells as edges with min length is
1538 * a dag on a chain. We then run network simplex, using
1539 * LR_balance.
1540 */
1541void sizeArray(htmltbl_t * tbl)
1542{
1543 graph_t *rowg;
1544 graph_t *colg;
1545#ifdef _WIN32
1546 Agdesc_t dir = { 1, 1, 0, 1 };
1547#else
1548 Agdesc_t dir = Agstrictdirected;
1549#endif
1550
1551 /* Do the 1D cases by hand */
1552 if ((tbl->rc == 1) || (tbl->cc == 1)) {
1553 sizeLinearArray(tbl);
1554 return;
1555 }
1556
1557 tbl->heights = N_NEW(tbl->rc + 1, int);
1558 tbl->widths = N_NEW(tbl->cc + 1, int);
1559
1560 rowg = agopen("rowg", dir, NIL(Agdisc_t *));
1561 colg = agopen("colg", dir, NIL(Agdisc_t *));
1562 /* Only need GD_nlist */
1563 agbindrec(rowg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE); // graph custom data
1564 agbindrec(colg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE); // graph custom data
1565 makeGraphs(tbl, rowg, colg);
1566 rank(rowg, 2, INT_MAX);
1567 rank(colg, 2, INT_MAX);
1568 setSizes(tbl, rowg, colg);
1569 closeGraphs(rowg, colg);
1570}
1571
1572static void pos_html_tbl(htmltbl_t *, boxf, int); /* forward declaration */
1573
1574/* pos_html_img:
1575 * Place image in cell
1576 * storing allowed space handed by parent cell.
1577 * How this space is used is handled in emit_html_img.
1578 */
1579static void pos_html_img(htmlimg_t * cp, boxf pos)
1580{
1581 cp->box = pos;
1582}
1583
1584/* pos_html_txt:
1585 * Set default alignment.
1586 */
1587static void pos_html_txt(htmltxt_t * ftxt, char c)
1588{
1589 int i;
1590
1591 for (i = 0; i < ftxt->nspans; i++) {
1592 if (ftxt->spans[i].just == UNSET_ALIGN) /* unset */
1593 ftxt->spans[i].just = c;
1594 }
1595}
1596
1597/* pos_html_cell:
1598 */
1599static void pos_html_cell(htmlcell_t * cp, boxf pos, int sides)
1600{
1601 double delx, dely;
1602 pointf oldsz;
1603 boxf cbox;
1604
1605 if (!cp->data.pencolor && cp->parent->data.pencolor)
1606 cp->data.pencolor = strdup(cp->parent->data.pencolor);
1607
1608 /* If fixed, align cell */
1609 if (cp->data.flags & FIXED_FLAG) {
1610 oldsz = cp->data.box.UR;
1611 delx = (pos.UR.x - pos.LL.x) - oldsz.x;
1612 if (delx > 0) {
1613 switch (cp->data.flags & HALIGN_MASK) {
1614 case HALIGN_LEFT:
1615 pos.UR.x = pos.LL.x + oldsz.x;
1616 break;
1617 case HALIGN_RIGHT:
1618 pos.UR.x += delx;
1619 pos.LL.x += delx;
1620 break;
1621 default:
1622 pos.LL.x += delx / 2;
1623 pos.UR.x -= delx / 2;
1624 break;
1625 }
1626 }
1627 dely = (pos.UR.y - pos.LL.y) - oldsz.y;
1628 if (dely > 0) {
1629 switch (cp->data.flags & VALIGN_MASK) {
1630 case VALIGN_BOTTOM:
1631 pos.UR.y = pos.LL.y + oldsz.y;
1632 break;
1633 case VALIGN_TOP:
1634 pos.UR.y += dely;
1635 pos.LL.y += dely;
1636 break;
1637 default:
1638 pos.LL.y += dely / 2;
1639 pos.UR.y -= dely / 2;
1640 break;
1641 }
1642 }
1643 }
1644 cp->data.box = pos;
1645 cp->data.sides = sides;
1646
1647 /* set up child's position */
1648 cbox.LL.x = pos.LL.x + cp->data.border + cp->data.pad;
1649 cbox.LL.y = pos.LL.y + cp->data.border + cp->data.pad;
1650 cbox.UR.x = pos.UR.x - cp->data.border - cp->data.pad;
1651 cbox.UR.y = pos.UR.y - cp->data.border - cp->data.pad;
1652
1653 if (cp->child.kind == HTML_TBL) {
1654 pos_html_tbl(cp->child.u.tbl, cbox, sides);
1655 } else if (cp->child.kind == HTML_IMAGE) {
1656 /* Note that alignment trumps scaling */
1657 oldsz = cp->child.u.img->box.UR;
1658 delx = (cbox.UR.x - cbox.LL.x) - oldsz.x;
1659 if (delx > 0) {
1660 switch (cp->data.flags & HALIGN_MASK) {
1661 case HALIGN_LEFT:
1662 cbox.UR.x -= delx;
1663 break;
1664 case HALIGN_RIGHT:
1665 cbox.LL.x += delx;
1666 break;
1667 }
1668 }
1669
1670 dely = (cbox.UR.y - cbox.LL.y) - oldsz.y;
1671 if (dely > 0) {
1672 switch (cp->data.flags & VALIGN_MASK) {
1673 case VALIGN_BOTTOM:
1674 cbox.UR.y -= dely;
1675 break;
1676 case VALIGN_TOP:
1677 cbox.LL.y += dely;
1678 break;
1679 }
1680 }
1681 pos_html_img(cp->child.u.img, cbox);
1682 } else {
1683 char dfltalign;
1684 int af;
1685
1686 oldsz = cp->child.u.txt->box.UR;
1687 delx = (cbox.UR.x - cbox.LL.x) - oldsz.x;
1688 /* If the cell is larger than the text block and alignment is
1689 * done at textblock level, the text box is shrunk accordingly.
1690 */
1691 if ((delx > 0)
1692 && ((af = (cp->data.flags & HALIGN_MASK)) != HALIGN_TEXT)) {
1693 switch (af) {
1694 case HALIGN_LEFT:
1695 cbox.UR.x -= delx;
1696 break;
1697 case HALIGN_RIGHT:
1698 cbox.LL.x += delx;
1699 break;
1700 default:
1701 cbox.LL.x += delx / 2;
1702 cbox.UR.x -= delx / 2;
1703 break;
1704 }
1705 }
1706
1707 dely = (cbox.UR.y - cbox.LL.y) - oldsz.y;
1708 if (dely > 0) {
1709 switch (cp->data.flags & VALIGN_MASK) {
1710 case VALIGN_BOTTOM:
1711 cbox.UR.y -= dely;
1712 break;
1713 case VALIGN_TOP:
1714 cbox.LL.y += dely;
1715 break;
1716 default:
1717 cbox.LL.y += dely / 2;
1718 cbox.UR.y -= dely / 2;
1719 break;
1720 }
1721 }
1722 cp->child.u.txt->box = cbox;
1723
1724 /* Set default text alignment
1725 */
1726 switch (cp->data.flags & BALIGN_MASK) {
1727 case BALIGN_LEFT:
1728 dfltalign = 'l';
1729 break;
1730 case BALIGN_RIGHT:
1731 dfltalign = 'r';
1732 break;
1733 default:
1734 dfltalign = 'n';
1735 break;
1736 }
1737 pos_html_txt(cp->child.u.txt, dfltalign);
1738 }
1739}
1740
1741/* pos_html_tbl:
1742 * Position table given its box, then calculate
1743 * the position of each cell. In addition, set the sides
1744 * attribute indicating which external sides of the node
1745 * are accessible to the table.
1746 */
1747static void pos_html_tbl(htmltbl_t * tbl, boxf pos, int sides)
1748{
1749 int x, y, delx, dely, oldsz;
1750 int i, extra, plus;
1751 htmlcell_t **cells = tbl->u.n.cells;
1752 htmlcell_t *cp;
1753 boxf cbox;
1754
1755 if (tbl->u.n.parent && tbl->u.n.parent->data.pencolor
1756 && !tbl->data.pencolor)
1757 tbl->data.pencolor = strdup(tbl->u.n.parent->data.pencolor);
1758
1759 oldsz = tbl->data.box.UR.x;
1760 delx = (pos.UR.x - pos.LL.x) - oldsz;
1761 assert(delx >= 0);
1762 oldsz = tbl->data.box.UR.y;
1763 dely = (pos.UR.y - pos.LL.y) - oldsz;
1764 assert(dely >= 0);
1765
1766 /* If fixed, align box */
1767 if (tbl->data.flags & FIXED_FLAG) {
1768 if (delx > 0) {
1769 switch (tbl->data.flags & HALIGN_MASK) {
1770 case HALIGN_LEFT:
1771 pos.UR.x = pos.LL.x + oldsz;
1772 break;
1773 case HALIGN_RIGHT:
1774 pos.UR.x += delx;
1775 pos.LL.x += delx;
1776 break;
1777 default:
1778 pos.LL.x += delx / 2;
1779 pos.UR.x -= delx / 2;
1780 break;
1781 }
1782 delx = 0;
1783 }
1784 if (dely > 0) {
1785 switch (tbl->data.flags & VALIGN_MASK) {
1786 case VALIGN_BOTTOM:
1787 pos.UR.y = pos.LL.y + oldsz;
1788 break;
1789 case VALIGN_TOP:
1790 pos.LL.y += dely;
1791 pos.UR.y = pos.LL.y + oldsz;
1792 break;
1793 default:
1794 pos.LL.y += dely / 2;
1795 pos.UR.y -= dely / 2;
1796 break;
1797 }
1798 dely = 0;
1799 }
1800 }
1801
1802 /* change sizes to start positions and distribute extra space */
1803 x = pos.LL.x + tbl->data.border + tbl->data.space;
1804 extra = delx / (tbl->cc);
1805 plus = ROUND(delx - extra * (tbl->cc));
1806 for (i = 0; i <= tbl->cc; i++) {
1807 delx = tbl->widths[i] + extra + (i < plus ? 1 : 0);
1808 tbl->widths[i] = x;
1809 x += delx + tbl->data.space;
1810 }
1811 y = pos.UR.y - tbl->data.border - tbl->data.space;
1812 extra = dely / (tbl->rc);
1813 plus = ROUND(dely - extra * (tbl->rc));
1814 for (i = 0; i <= tbl->rc; i++) {
1815 dely = tbl->heights[i] + extra + (i < plus ? 1 : 0);
1816 tbl->heights[i] = y;
1817 y -= dely + tbl->data.space;
1818 }
1819
1820 while ((cp = *cells++)) {
1821 int mask = 0;
1822 if (sides) {
1823 if (cp->col == 0)
1824 mask |= LEFT;
1825 if (cp->row == 0)
1826 mask |= TOP;
1827 if (cp->col + cp->cspan == tbl->cc)
1828 mask |= RIGHT;
1829 if (cp->row + cp->rspan == tbl->rc)
1830 mask |= BOTTOM;
1831 }
1832 cbox.LL.x = tbl->widths[cp->col];
1833 cbox.UR.x = tbl->widths[cp->col + cp->cspan] - tbl->data.space;
1834 cbox.UR.y = tbl->heights[cp->row];
1835 cbox.LL.y = tbl->heights[cp->row + cp->rspan] + tbl->data.space;
1836 pos_html_cell(cp, cbox, sides & mask);
1837 }
1838
1839 tbl->data.sides = sides;
1840 tbl->data.box = pos;
1841}
1842
1843/* size_html_tbl:
1844 * Determine the size of a table by first determining the
1845 * size of each cell.
1846 */
1847static int
1848size_html_tbl(graph_t * g, htmltbl_t * tbl, htmlcell_t * parent,
1849 htmlenv_t * env)
1850{
1851 int i, wd, ht;
1852 int rv = 0;
1853 static textfont_t savef;
1854
1855 if (tbl->font)
1856 pushFontInfo(env, tbl->font, &savef);
1857 tbl->u.n.parent = parent;
1858 rv = processTbl(g, tbl, env);
1859
1860 /* Set up border and spacing */
1861 if (!(tbl->data.flags & SPACE_SET)) {
1862 tbl->data.space = DEFAULT_CELLSPACING;
1863 }
1864 if (!(tbl->data.flags & BORDER_SET)) {
1865 tbl->data.border = DEFAULT_BORDER;
1866 }
1867
1868 sizeArray(tbl);
1869
1870 wd = (tbl->cc + 1) * tbl->data.space + 2 * tbl->data.border;
1871 ht = (tbl->rc + 1) * tbl->data.space + 2 * tbl->data.border;
1872 for (i = 0; i < tbl->cc; i++)
1873 wd += tbl->widths[i];
1874 for (i = 0; i < tbl->rc; i++)
1875 ht += tbl->heights[i];
1876
1877 if (tbl->data.flags & FIXED_FLAG) {
1878 if (tbl->data.width && tbl->data.height) {
1879 if ((tbl->data.width < wd) || (tbl->data.height < ht)) {
1880 agerr(AGWARN, "table size too small for content\n");
1881 rv = 1;
1882 }
1883 wd = ht = 0;
1884 } else {
1885 agerr(AGWARN,
1886 "fixed table size with unspecified width or height\n");
1887 rv = 1;
1888 }
1889 }
1890 tbl->data.box.UR.x = MAX(wd, tbl->data.width);
1891 tbl->data.box.UR.y = MAX(ht, tbl->data.height);
1892
1893 if (tbl->font)
1894 popFontInfo(env, &savef);
1895 return rv;
1896}
1897
1898static char *nameOf(void *obj, agxbuf * xb)
1899{
1900 Agedge_t *ep;
1901 switch (agobjkind(obj)) {
1902 case AGRAPH:
1903 agxbput(xb, agnameof(((Agraph_t *) obj)));
1904 break;
1905 case AGNODE:
1906 agxbput(xb, agnameof(((Agnode_t *) obj)));
1907 break;
1908 case AGEDGE:
1909 ep = (Agedge_t *) obj;
1910 agxbput(xb, agnameof(agtail(ep)));
1911 agxbput(xb, agnameof(aghead(ep)));
1912 if (agisdirected(agraphof(aghead(ep))))
1913 agxbput(xb, "->");
1914 else
1915 agxbput(xb, "--");
1916 break;
1917 }
1918 return agxbuse(xb);
1919}
1920
1921#ifdef DEBUG
1922void indent(int i)
1923{
1924 while (i--)
1925 fprintf(stderr, " ");
1926}
1927
1928void printBox(boxf b)
1929{
1930 fprintf(stderr, "(%f,%f)(%f,%f)", b.LL.x, b.LL.y, b.UR.x, b.UR.y);
1931}
1932
1933void printImage(htmlimg_t * ip, int ind)
1934{
1935 indent(ind);
1936 fprintf(stderr, "img: %s\n", ip->src);
1937}
1938
1939void printTxt(htmltxt_t * txt, int ind)
1940{
1941 int i, j;
1942
1943 indent(ind);
1944 fprintf(stderr, "txt spans = %d \n", txt->nspans);
1945 for (i = 0; i < txt->nspans; i++) {
1946 indent(ind + 1);
1947 fprintf(stderr, "[%d] %d items\n", i, txt->spans[i].nitems);
1948 for (j = 0; j < txt->spans[i].nitems; j++) {
1949 indent(ind + 2);
1950 fprintf(stderr, "[%d] (%f,%f) \"%s\" ",
1951 j, txt->spans[i].items[j].size.x,
1952 txt->spans[i].items[j].size.y,
1953 txt->spans[i].items[j].str);
1954 if (txt->spans[i].items[j].font)
1955 fprintf(stderr, "font %s color %s size %f\n",
1956 txt->spans[i].items[j].font->name,
1957 txt->spans[i].items[j].font->color,
1958 txt->spans[i].items[j].font->size);
1959 else
1960 fprintf(stderr, "\n");
1961 }
1962 }
1963}
1964
1965void printData(htmldata_t * dp)
1966{
1967 unsigned char flags = dp->flags;
1968 char c;
1969
1970 fprintf(stderr, "s%d(%d) ", dp->space, (flags & SPACE_SET ? 1 : 0));
1971 fprintf(stderr, "b%d(%d) ", dp->border, (flags & BORDER_SET ? 1 : 0));
1972 fprintf(stderr, "p%d(%d) ", dp->pad, (flags & PAD_SET ? 1 : 0));
1973 switch (flags & HALIGN_MASK) {
1974 case HALIGN_RIGHT:
1975 c = 'r';
1976 break;
1977 case HALIGN_LEFT:
1978 c = 'l';
1979 break;
1980 default:
1981 c = 'n';
1982 break;
1983 }
1984 fprintf(stderr, "%c", c);
1985 switch (flags & VALIGN_MASK) {
1986 case VALIGN_TOP:
1987 c = 't';
1988 break;
1989 case VALIGN_BOTTOM:
1990 c = 'b';
1991 break;
1992 default:
1993 c = 'c';
1994 break;
1995 }
1996 fprintf(stderr, "%c ", c);
1997 printBox(dp->box);
1998}
1999
2000void printTbl(htmltbl_t * tbl, int ind)
2001{
2002 htmlcell_t **cells = tbl->u.n.cells;
2003 indent(ind);
2004 fprintf(stderr, "tbl (%p) %d %d ", tbl, tbl->cc, tbl->rc);
2005 printData(&tbl->data);
2006 fputs("\n", stderr);
2007 while (*cells)
2008 printCell(*cells++, ind + 1);
2009}
2010
2011static void printCell(htmlcell_t * cp, int ind)
2012{
2013 indent(ind);
2014 fprintf(stderr, "cell %d %d %d %d ", cp->cspan, cp->rspan, cp->col,
2015 cp->row);
2016 printData(&cp->data);
2017 fputs("\n", stderr);
2018 switch (cp->child.kind) {
2019 case HTML_TBL:
2020 printTbl(cp->child.u.tbl, ind + 1);
2021 break;
2022 case HTML_TEXT:
2023 printTxt(cp->child.u.txt, ind + 1);
2024 break;
2025 case HTML_IMAGE:
2026 printImage(cp->child.u.img, ind + 1);
2027 break;
2028 default:
2029 break;
2030 }
2031}
2032
2033void printLbl(htmllabel_t * lbl)
2034{
2035 if (lbl->kind == HTML_TBL)
2036 printTbl(lbl->u.tbl, 0);
2037 else
2038 printTxt(lbl->u.txt, 0);
2039}
2040#endif /* DEBUG */
2041
2042static char *getPenColor(void *obj)
2043{
2044 char *str;
2045
2046 if (((str = agget(obj, "pencolor")) != 0) && str[0])
2047 return str;
2048 else if (((str = agget(obj, "color")) != 0) && str[0])
2049 return str;
2050 else
2051 return NULL;
2052}
2053
2054/* make_html_label:
2055 * Return non-zero if problem parsing HTML. In this case, use object name.
2056 */
2057int make_html_label(void *obj, textlabel_t * lp)
2058{
2059 int rv;
2060 double wd2, ht2;
2061 boxf box;
2062 graph_t *g;
2063 htmllabel_t *lbl;
2064 htmlenv_t env;
2065 char *s;
2066
2067 env.obj = obj;
2068 switch (agobjkind(obj)) {
2069 case AGRAPH:
2070 env.g = ((Agraph_t *) obj)->root;
2071 break;
2072 case AGNODE:
2073 env.g = agraphof(((Agnode_t *) obj));
2074 break;
2075 case AGEDGE:
2076 env.g = agraphof(aghead(((Agedge_t *) obj)));
2077 break;
2078 }
2079 g = env.g->root;
2080
2081 env.finfo.size = lp->fontsize;
2082 env.finfo.name = lp->fontname;
2083 env.finfo.color = lp->fontcolor;
2084 env.finfo.flags = 0;
2085 lbl = parseHTML(lp->text, &rv, &env);
2086 if (!lbl) {
2087 /* Parse of label failed; revert to simple text label */
2088 agxbuf xb;
2089 unsigned char buf[SMALLBUF];
2090 agxbinit(&xb, SMALLBUF, buf);
2091 lp->html = FALSE;
2092 lp->text = strdup(nameOf(obj, &xb));
2093 switch (lp->charset) {
2094 case CHAR_LATIN1:
2095 s = latin1ToUTF8(lp->text);
2096 break;
2097 default: /* UTF8 */
2098 s = htmlEntityUTF8(lp->text, env.g);
2099 break;
2100 }
2101 free(lp->text);
2102 lp->text = s;
2103 make_simple_label(GD_gvc(g), lp);
2104 agxbfree(&xb);
2105 return rv;
2106 }
2107
2108 if (lbl->kind == HTML_TBL) {
2109 if (!lbl->u.tbl->data.pencolor && getPenColor(obj))
2110 lbl->u.tbl->data.pencolor = strdup(getPenColor(obj));
2111 rv |= size_html_tbl(g, lbl->u.tbl, NULL, &env);
2112 wd2 = (lbl->u.tbl->data.box.UR.x) / 2;
2113 ht2 = (lbl->u.tbl->data.box.UR.y) / 2;
2114 box = boxfof(-wd2, -ht2, wd2, ht2);
2115 pos_html_tbl(lbl->u.tbl, box, BOTTOM | RIGHT | TOP | LEFT);
2116 lp->dimen.x = box.UR.x - box.LL.x;
2117 lp->dimen.y = box.UR.y - box.LL.y;
2118 } else {
2119 rv |= size_html_txt(GD_gvc(g), lbl->u.txt, &env);
2120 wd2 = lbl->u.txt->box.UR.x / 2;
2121 ht2 = lbl->u.txt->box.UR.y / 2;
2122 box = boxfof(-wd2, -ht2, wd2, ht2);
2123 lbl->u.txt->box = box;
2124 lp->dimen.x = box.UR.x - box.LL.x;
2125 lp->dimen.y = box.UR.y - box.LL.y;
2126 }
2127
2128 lp->u.html = lbl;
2129
2130 /* If the label is a table, replace label text because this may
2131 * be used for the title and alt fields in image maps.
2132 */
2133 if (lbl->kind == HTML_TBL) {
2134 free(lp->text);
2135 lp->text = strdup("<TABLE>");
2136 }
2137
2138 return rv;
2139}
2140