Graphviz  2.41.20170921.2350
htmltable.c
Go to the documentation of this file.
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;
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
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  */
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
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 
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;
166  if (simple)
168  else
169  tl.yoffset_centerline = 1;
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 
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 
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  */
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  */
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
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;
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;
292  AF[0] = AF[4];
293  gvrender_polyline(job, AF, 3);
294  break;
296  gvrender_polyline(job, AF+1, 4);
297  break;
299  AF[5] = AF[1];
300  gvrender_polyline(job, AF+2, 4);
301  break;
303  AF[5] = AF[1];
304  AF[6] = AF[2];
305  gvrender_polyline(job, AF+3, 4);
306  break;
308  AF[0] = AF[4];
309  gvrender_polyline(job, AF, 4);
310  break;
312  gvrender_polyline(job, AF+1, 2);
313  gvrender_polyline(job, AF+3, 2);
314  break;
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
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
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);
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  */
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
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 
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  */
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 
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  */
686 static void allocObj(GVJ_t * job)
687 {
688  obj_state_t *obj;
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 
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
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  */
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. */
781  if (tbl->data.pencolor)
783  else
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 
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 
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 
832 {
833  free(ip->src);
834  free(ip);
835 }
836 
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  */
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 
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 
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  */
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  */
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  */
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 
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;
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  */
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
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
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 
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  */
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  */
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  */
1378 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  */
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  */
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
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  */
1456 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  */
1513 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  */
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  */
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  */
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  */
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  */
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.UR.y += dely;
1791  pos.LL.y += dely;
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
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)) {
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 
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
1922 void indent(int i)
1923 {
1924  while (i--)
1925  fprintf(stderr, " ");
1926 }
1927 
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 
1933 void printImage(htmlimg_t * ip, int ind)
1934 {
1935  indent(ind);
1936  fprintf(stderr, "img: %s\n", ip->src);
1937 }
1938 
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 
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 
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 
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 
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 
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  */
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 }
#define DOTTED
Definition: const.h:216
char * tooltip
Definition: htmltable.c:48
#define HTML_VRULE
Definition: htmltable.h:95
void freePS(PointSet *ps)
Definition: pointset.c:68
PointSet * newPS(void)
Definition: pointset.c:63
#define MAX(a, b)
Definition: agerror.c:17
unsigned short rspan
Definition: htmltable.h:134
pointf size
Definition: textspan.h:64
double size
Definition: htmltable.h:53
CGRAPH_API int agobjkind(void *)
Definition: obj.c:264
#define ND_rank(n)
Definition: types.h:529
void gvrender_box(GVJ_t *job, boxf BF, int filled)
Definition: gvrender.c:603
CGRAPH_API Agnode_t * agnode(Agraph_t *g, char *name, int createflag)
Definition: node.c:142
Definition: cgraph.h:388
Definition: types.h:67
CGRAPH_API Agraph_t * agopen(char *name, Agdesc_t desc, Agdisc_t *disc)
Definition: graph.c:44
#define GD_nlist(g)
Definition: types.h:401
CDT_API int dtclose(Dt_t *)
int * widths
Definition: htmltable.h:115
#define PAD_SET
Definition: htmltable.h:30
#define N_NEW(n, t)
Definition: memory.h:36
#define INVISIBLE
Definition: const.h:214
#define elist_append(item, L)
Definition: types.h:272
char * latin1ToUTF8(char *s)
Definition: utils.c:1561
#define agxbuse(X)
Definition: agxbuf.h:83
#define BORDER_LEFT
Definition: htmltable.h:35
htmllabel_t * html
Definition: types.h:139
char * htmlEntityUTF8(char *s, graph_t *g)
Definition: utils.c:1473
htmldata_t data
Definition: htmltable.h:102
#define UNSET_ALIGN
Definition: htmltable.h:41
htmltxt_t * txt
Definition: htmltable.h:125
#define SMALLBUF
Definition: const.h:17
boolean findStopColor(char *colorlist, char *clrs[2], float *frac)
Definition: emit.c:4223
CDT_API Dtlink_t * dtflatten(Dt_t *)
Definition: dtflatten.c:9
Dt_t * textfont_dt
Definition: gvcint.h:96
signed char space
Definition: htmltable.h:79
int initMapData(GVJ_t *job, char *lbl, char *url, char *tooltip, char *target, char *id, void *gobj)
Definition: emit.c:146
pointf pos
Definition: htmltable.h:157
double size
Definition: textspan.h:52
char * objid
Definition: htmltable.h:162
#define ED_head_port(e)
Definition: types.h:591
union pitem::@16 u
void gvrender_usershape(GVJ_t *job, char *name, pointf *AF, int n, boolean filled, char *imagescale, char *imagepos)
Definition: gvrender.c:718
#define ROUND(f)
Definition: arith.h:84
#define assert(x)
Definition: cghdr.h:47
char * port
Definition: htmltable.h:72
Definition: geom.h:28
char * text
Definition: types.h:124
CGRAPH_API int agisdirected(Agraph_t *g)
Definition: graph.c:182
char * fontcolor
Definition: types.h:126
size_t agxbput(agxbuf *xb, const char *s)
Definition: agxbuf.c:84
void sizeArray(htmltbl_t *tbl)
Definition: htmltable.c:1541
#define RIGHT
Definition: const.h:119
void gvrender_set_fillcolor(GVJ_t *job, char *name)
Definition: gvrender.c:481
htmllabel_t * parseHTML(char *txt, int *warn, htmlenv_t *env)
Definition: htmlparse.c:2516
char simple
Definition: htmltable.h:60
struct htmltbl_t::@12::@13 n
#define RESET(fld)
Definition: htmltable.c:419
#define TOP
Definition: const.h:120
void gvrender_end_label(GVJ_t *job)
Definition: gvrender.c:435
union htmltbl_t::@12 u
#define HTML_TBL
Definition: htmltable.h:91
unsigned short cspan
Definition: htmltable.h:133
#define HALIGN_TEXT
Definition: htmltable.h:25
unsigned short col
Definition: htmltable.h:135
int make_html_label(void *obj, textlabel_t *lp)
Definition: htmltable.c:2057
unsigned char border
Definition: htmltable.h:80
#define alloc_elist(n, L)
Definition: types.h:273
int agerr(agerrlevel_t level, const char *fmt,...)
Definition: agerror.c:141
int flags
Definition: gvcjob.h:308
double fontsize
Definition: types.h:128
boxf box
Definition: htmltable.h:61
void gvrender_begin_label(GVJ_t *job, label_type type)
Definition: gvrender.c:425
#define RADIAL
Definition: const.h:210
boxf box
Definition: htmltable.h:65
int html_path(node_t *n, port *p, int side, boxf *rv, int *k)
Definition: htmltable.c:959
#define BORDER_TOP
Definition: htmltable.h:36
#define BORDER_RIGHT
Definition: htmltable.h:37
#define parent(i)
Definition: closest.c:88
Definition: gvcjob.h:271
#define HALIGN_RIGHT
Definition: htmltable.h:22
char * scale
Definition: htmltable.h:67
#define ND_shape_info(n)
Definition: types.h:535
char * name
Definition: textspan.h:49
point UR
Definition: geom.h:33
#define SPLIT(x, n, s)
Definition: htmltable.c:1328
int x
Definition: geom.h:26
char * strdup_and_subst_obj(char *str, void *obj)
Definition: labels.c:451
#define ND_label(n)
Definition: types.h:509
#define GD_gvc(g)
Definition: types.h:358
#define HALIGN_LEFT
Definition: htmltable.h:23
void free_html_text(htmltxt_t *t)
Definition: htmltable.c:805
Definition: cgraph.h:388
obj_state_t * obj
Definition: gvcjob.h:278
char * agget(void *obj, char *name)
Definition: attr.c:428
char * pencolor
Definition: htmltable.h:77
CGRAPH_API Agraph_t * agraphof(void *obj)
Definition: obj.c:185
CGRAPH_API Agnode_t * agtail(Agedge_t *e)
Definition: edge.c:525
point gvusershape_size(graph_t *g, char *name)
Definition: gvusershape.c:730
#define HTML_IMAGE
Definition: htmltable.h:93
#define VALIGN_MASK
Definition: htmltable.h:28
#define BALIGN_RIGHT
Definition: htmltable.h:32
CGRAPH_API Agdesc_t Agstrictdirected
Definition: cgraph.h:419
void pop_obj_state(GVJ_t *job)
Definition: emit.c:113
char * imgscale
Definition: htmltable.h:161
graph_t * g
Definition: htmltable.h:160
char * str
Definition: textspan.h:59
unsigned short flags
Definition: htmltable.h:83
int gradientangle
Definition: htmltable.h:78
boxf box
Definition: htmltable.h:87
#define ED_tail_port(e)
Definition: types.h:600
char * tooltip
Definition: gvcjob.h:225
void free_html_label(htmllabel_t *lp, int root)
Definition: htmltable.c:870
int rank(graph_t *g, int balance, int maxiter)
Definition: ns.c:866
int charset
Definition: types.h:127
pointf pos
Definition: types.h:133
unsigned int flags
Definition: textspan.h:53
#define NIL(t)
Definition: dthdr.h:13
unsigned short width
Definition: htmltable.h:84
void * obj
Definition: htmltable.h:159
CGRAPH_API Agnode_t * aghead(Agedge_t *e)
Definition: edge.c:533
char * href
Definition: htmltable.h:71
#define BORDER_BOTTOM
Definition: htmltable.h:38
double y
Definition: geom.h:28
void emit_map_rect(GVJ_t *job, boxf b)
Definition: emit.c:671
#define ROUNDED
Definition: const.h:211
Definition: gvcint.h:70
CGRAPH_API int agclose(Agraph_t *g)
Definition: graph.c:93
CGRAPH_API char * agnameof(void *)
Definition: id.c:143
obj_type type
Definition: gvcjob.h:193
#define HTML_TEXT
Definition: htmltable.h:92
#define DEFAULT_COLOR
Definition: const.h:51
void gvrender_begin_anchor(GVJ_t *job, char *href, char *tooltip, char *target, char *id)
Definition: gvrender.c:404
char * url
Definition: gvcjob.h:219
void gvrender_set_pencolor(GVJ_t *job, char *name)
Definition: gvrender.c:464
int inIntSet(Dt_t *is, int v)
Definition: intset.c:70
#define VALIGN_BOTTOM
Definition: htmltable.h:27
double lfsize
Definition: htmltable.h:54
#define B2BF(b, bf)
Definition: geom.h:74
htmllabel_t * lbl
Definition: htmlparse.c:81
#define SPACE_SET
Definition: htmltable.h:31
htmlcell_t * cp
Definition: htmltable.h:151
int isInPS(PointSet *ps, int x, int y)
Definition: pointset.c:101
unsigned short style
Definition: htmltable.h:86
#define LEFT
Definition: const.h:121
double yoffset_centerline
Definition: textspan.h:63
#define GRADIENT
Definition: const.h:254
char valign
Definition: types.h:141
short nitems
Definition: htmltable.h:51
unsigned short row
Definition: htmltable.h:136
PostscriptAlias * postscript_alias
Definition: textspan.h:51
point LL
Definition: geom.h:33
void agxbinit(agxbuf *xb, unsigned int hint, unsigned char *init)
Definition: agxbuf.c:25
struct htmltbl_t::@12::@14 p
GVC_t * gvc
Definition: gvcjob.h:272
void gvrender_set_style(GVJ_t *job, char **s)
Definition: gvrender.c:512
#define DEFAULT_CELLSPACING
Definition: htmltable.c:44
Definition: grammar.c:79
#define BOTTOM
Definition: const.h:118
pointf dimen
Definition: types.h:129
#define AGNODE
Definition: cgraph.h:101
#define GD_flip(g)
Definition: types.h:385
#define BALIGN_MASK
Definition: htmltable.h:34
void gvrender_set_gradient_vals(GVJ_t *job, char *stopcolor, int angle, float frac)
Definition: gvrender.c:498
void makeGraphs(htmltbl_t *tbl, graph_t *rowg, graph_t *colg)
Definition: htmltable.c:1456
char * color
Definition: textspan.h:50
emit_state_t emit_state
Definition: gvcjob.h:201
signed char cb
Definition: htmltable.h:113
graph_t * g
Definition: gvcjob.h:195
unsigned char sides
Definition: htmltable.h:82
char * src
Definition: htmltable.h:66
void gvrender_set_penwidth(GVJ_t *job, double penwidth)
Definition: gvrender.c:848
if(aagss+aagstacksize-1<=aagssp)
Definition: grammar.c:1332
#define dtinsert(d, o)
Definition: cdt.h:262
unsigned char pad
Definition: htmltable.h:81
obj_state_t * push_obj_state(GVJ_t *job)
Definition: emit.c:84
void gvrender_textspan(GVJ_t *job, pointf p, textspan_t *span)
Definition: gvrender.c:445
#define agfindnode(g, n)
Definition: types.h:611
htmllabel_t child
Definition: htmltable.h:137
void free_html_data(htmldata_t *dp)
Definition: htmltable.c:794
#define NULL
Definition: logic.h:39
#define BALIGN_LEFT
Definition: htmltable.h:33
#define RGRADIENT
Definition: const.h:255
void addPS(PointSet *ps, int x, int y)
Definition: pointset.c:82
double yoffset_layout
Definition: textspan.h:63
char * getObjId(GVJ_t *job, void *obj, agxbuf *xb)
Definition: emit.c:198
union htmllabel_t::@15 u
textfont_t * font
Definition: htmltable.h:118
graph_t * sg
Definition: gvcjob.h:196
Definition: geom.h:26
#define ND_in(n)
Definition: types.h:507
double x
Definition: geom.h:28
#define DEFAULT_BORDER
Definition: htmltable.c:42
#define HTML_HRULE
Definition: htmltable.h:96
void round_corners(GVJ_t *job, pointf *AF, int sides, int style, int filled)
Definition: shapes.c:516
#define ND_next(n)
Definition: types.h:517
obj_state_t * parent
Definition: gvcjob.h:191
#define CHAR_LATIN1
Definition: const.h:205
void emit_html_label(GVJ_t *job, htmllabel_t *lp, textlabel_t *tp)
Definition: htmltable.c:747
GVC_t * gvc
Definition: htmlparse.c:87
for(;;)
Definition: grammar.c:1846
int strcasecmp(const char *s1, const char *s2)
Definition: strcasecmp.c:21
char * title
Definition: htmltable.h:74
char just
Definition: textspan.h:65
unsigned short height
Definition: htmltable.h:85
char * target
Definition: htmltable.c:49
#define ND_out(n)
Definition: types.h:522
htmldata_t data
Definition: htmltable.h:132
pointf LL
Definition: geom.h:35
#define FIXED_FLAG
Definition: htmltable.h:21
CGRAPH_API Agedge_t * agedge(Agraph_t *g, Agnode_t *t, Agnode_t *h, char *name, int createflag)
Definition: edge.c:281
htmltbl_t * parent
Definition: htmltable.h:138
union obj_state_s::@23 u
htmlimg_t * img
Definition: htmltable.h:126
#define INSIDE(p, b)
Definition: geom.h:39
#define FILL
Definition: const.h:253
boolean explicit_tooltip
Definition: htmltable.c:51
#define DASHED
Definition: const.h:217
void(* free_layout)(void *layout)
Definition: textspan.h:62
char * target
Definition: gvcjob.h:230
CGRAPH_API void * agbindrec(void *obj, char *name, unsigned int size, int move_to_front)
Definition: rec.c:86
boxf * html_port(node_t *n, char *pname, int *sides)
Definition: htmltable.c:928
void setSizes(htmltbl_t *tbl, graph_t *rowg, graph_t *colg)
Definition: htmltable.c:1513
pointf textspan_size(GVC_t *gvc, textspan_t *span)
Definition: textspan.c:198
unsigned char ruled
Definition: htmltable.h:139
htmltbl_t * tbl
Definition: htmltable.h:124
#define agfindedge(g, t, h)
Definition: types.h:610
Definition: cdt.h:99
Dt_t * openIntSet(void)
Definition: intset.c:55
void sizeLinearArray(htmltbl_t *tbl)
Definition: htmltable.c:1336
char * nToName(int c)
Definition: htmltable.c:1378
edge_t * e
Definition: gvcjob.h:198
char * url
Definition: htmltable.c:47
agxbuf * str
Definition: htmlparse.c:85
char * target
Definition: htmltable.h:73
void make_simple_label(GVC_t *gvc, textlabel_t *lp)
Definition: labels.c:51
Definition: agxbuf.h:34
htextspan_t * spans
Definition: htmltable.h:58
Agraph_t * root
Definition: cgraph.h:247
char just
Definition: htmltable.h:52
#define GD_has_images(g)
Definition: types.h:373
char * id
Definition: gvcjob.h:220
Dt_t * rp
Definition: htmltable.h:150
node_t * n
Definition: gvcjob.h:197
short nspans
Definition: htmltable.h:59
#define EMIT_CLUSTERS_LAST
Definition: gvcjob.h:86
#define BORDER_MASK
Definition: htmltable.h:39
int explicit_tooltip
Definition: gvcjob.h:235
#define AGEDGE
Definition: cgraph.h:104
#define BORDER_SET
Definition: htmltable.h:29
char * bgcolor
Definition: htmltable.h:76
void gvrender_end_anchor(GVJ_t *job)
Definition: gvrender.c:415
int y
Definition: geom.h:26
#define DEFAULT_CELLPADDING
Definition: htmltable.c:43
union textlabel_t::@20 u
pointf UR
Definition: geom.h:35
textfont_t finfo
Definition: htmltable.h:158
#define dtlink(d, e)
Definition: cdt.h:250
char ** defaultlinestyle
Definition: gvcint.h:137
void agxbfree(agxbuf *xb)
Definition: agxbuf.c:94
#define GD_rankdir(g)
Definition: types.h:384
pointf space
Definition: types.h:130
Definition: geom.h:35
#define FALSE
Definition: cgraph.h:35
#define HALIGN_MASK
Definition: htmltable.h:24
#define VALIGN_TOP
Definition: htmltable.h:26
Definition: legal.c:60
#define free_list(L)
Definition: types.h:274
boolean objid_set
Definition: htmltable.h:163
textfont_t * font
Definition: textspan.h:60
int * heights
Definition: htmltable.h:114
#define ED_minlen(e)
Definition: types.h:595
Definition: geom.h:33
#define INT_MAX
Definition: arith.h:52
void free_html_img(htmlimg_t *ip)
Definition: htmltable.c:831
char * id
Definition: htmltable.h:75
void addIntSet(Dt_t *is, int v)
Definition: intset.c:61
void gvrender_polyline(GVJ_t *job, pointf *AF, int n)
Definition: gvrender.c:641
#define AGRAPH
Definition: cgraph.h:100
textspan_t * items
Definition: htmltable.h:50
void * layout
Definition: textspan.h:61
unsigned char ruled
Definition: htmltable.h:153
char * fontname
Definition: types.h:125
#define TRUE
Definition: cgraph.h:38