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 
46 typedef 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
57 static 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  */
65 static void
pushFontInfo(htmlenv_t * env,textfont_t * fp,textfont_t * savp)66 pushFontInfo(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  */
popFontInfo(htmlenv_t * env,textfont_t * savp)95 static 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 
105 static void
emit_htextspans(GVJ_t * job,int nspans,htextspan_t * spans,pointf p,double halfwidth_x,textfont_t finfo,boxf b,int simple)106 emit_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 
emit_html_txt(GVJ_t * job,htmltxt_t * tp,htmlenv_t * env)186 static 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 
doSide(GVJ_t * job,pointf p,double wd,double ht)203 static 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  */
mkPts(pointf * AF,boxf b,int border)219 static 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  */
doBorder(GVJ_t * job,htmldata_t * dp,boxf b)244 static 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  */
337 static int
setFill(GVJ_t * job,char * color,int angle,int style,char * clrs[2])338 setFill(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  */
371 static int
initAnchor(GVJ_t * job,htmlenv_t * env,htmldata_t * data,boxf b,htmlmap_data_t * save)372 initAnchor(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  */
endAnchor(GVJ_t * job,htmlmap_data_t * save)433 static 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 */
447 static 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  */
453 static void
emit_html_rules(GVJ_t * job,htmlcell_t * cp,htmlenv_t * env,char * color,htmlcell_t * nextc)454 emit_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 
emit_html_tbl(GVJ_t * job,htmltbl_t * tbl,htmlenv_t * env)521 static 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  */
emit_html_img(GVJ_t * job,htmlimg_t * cp,htmlenv_t * env)601 static 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 
emit_html_cell(GVJ_t * job,htmlcell_t * cp,htmlenv_t * env)628 static 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  */
allocObj(GVJ_t * job)686 static 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 
freeObj(GVJ_t * job)715 static 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 
726 static double
heightOfLbl(htmllabel_t * lp)727 heightOfLbl (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  */
emit_html_label(GVJ_t * job,htmllabel_t * lp,textlabel_t * tp)747 void 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 
free_html_data(htmldata_t * dp)794 void 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 
free_html_text(htmltxt_t * t)805 void 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 
free_html_img(htmlimg_t * ip)831 static void free_html_img(htmlimg_t * ip)
832 {
833     free(ip->src);
834     free(ip);
835 }
836 
free_html_cell(htmlcell_t * cp)837 static 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  */
free_html_tbl(htmltbl_t * tbl)849 static 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 
free_html_label(htmllabel_t * lp,int root)870 void 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 
882 static htmldata_t *portToTbl(htmltbl_t *, char *);	/* forward declaration */
883 
portToCell(htmlcell_t * cp,char * id)884 static 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  */
portToTbl(htmltbl_t * tp,char * id)902 static 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  */
html_port(node_t * n,char * pname,int * sides)928 boxf *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  */
html_path(node_t * n,port * p,int side,boxf * rv,int * k)959 int 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 
size_html_txt(GVC_t * gvc,htmltxt_t * ftxt,htmlenv_t * env)1004 static 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 */
1142 static int size_html_tbl(graph_t * g, htmltbl_t * tbl, htmlcell_t * parent,
1143 			 htmlenv_t * env);
1144 
1145 /* size_html_img:
1146  */
size_html_img(htmlimg_t * img,htmlenv_t * env)1147 static 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  */
1169 static int
size_html_cell(graph_t * g,htmlcell_t * cp,htmltbl_t * parent,htmlenv_t * env)1170 size_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 
findCol(PointSet * ps,int row,int col,htmlcell_t * cellp)1227 static 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  */
processTbl(graph_t * g,htmltbl_t * tbl,htmlenv_t * env)1259 static 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  */
sizeLinearArray(htmltbl_t * tbl)1336 static void 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 
1370 static 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  */
nToName(int c)1378 static char *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  */
closeGraphs(graph_t * rowg,graph_t * colg)1392 static 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  */
checkChain(graph_t * g)1408 static 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  */
1430 static void
checkEdge(graph_t * g,node_t * t,node_t * h,int sz)1431 checkEdge (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  */
makeGraphs(htmltbl_t * tbl,graph_t * rowg,graph_t * colg)1456 static void 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  */
setSizes(htmltbl_t * tbl,graph_t * rowg,graph_t * colg)1513 static void 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  */
sizeArray(htmltbl_t * tbl)1541 static void 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 
1572 static 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  */
pos_html_img(htmlimg_t * cp,boxf pos)1579 static 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  */
pos_html_txt(htmltxt_t * ftxt,char c)1587 static 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  */
pos_html_cell(htmlcell_t * cp,boxf pos,int sides)1599 static 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  */
pos_html_tbl(htmltbl_t * tbl,boxf pos,int sides)1747 static 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  */
1847 static int
size_html_tbl(graph_t * g,htmltbl_t * tbl,htmlcell_t * parent,htmlenv_t * env)1848 size_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 
nameOf(void * obj,agxbuf * xb)1898 static 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
indent(int i)1922 void indent(int i)
1923 {
1924     while (i--)
1925 	fprintf(stderr, "  ");
1926 }
1927 
printBox(boxf b)1928 void 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 
printImage(htmlimg_t * ip,int ind)1933 void printImage(htmlimg_t * ip, int ind)
1934 {
1935     indent(ind);
1936     fprintf(stderr, "img: %s\n", ip->src);
1937 }
1938 
printTxt(htmltxt_t * txt,int ind)1939 void 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 
printData(htmldata_t * dp)1965 void 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 
printTbl(htmltbl_t * tbl,int ind)2000 void 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 
printCell(htmlcell_t * cp,int ind)2011 static 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 
printLbl(htmllabel_t * lbl)2033 void 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 
getPenColor(void * obj)2042 static 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  */
make_html_label(void * obj,textlabel_t * lp)2057 int 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