Graphviz 16.1.1~dev.20261104.1411
Loading...
Searching...
No Matches
htmltable.c
Go to the documentation of this file.
1
3/*************************************************************************
4 * Copyright (c) 2011 AT&T Intellectual Property
5 * All rights reserved. This program and the accompanying materials
6 * are made available under the terms of the Eclipse Public License v2.0
7 * which accompanies this distribution, and is available at
8 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
9 *
10 * Contributors: Details at https://graphviz.org
11 *************************************************************************/
12
13// Implementation of HTML-like tables.
14//
15// The (now purged) CodeGen graphics model, especially with integral
16// coordinates, is not adequate to handle this as we would like. In particular,
17// it is difficult to handle notions of adjacency and correct rounding to
18// pixels. For example, if 2 adjacent boxes bb1.UR.x == bb2.LL.x, the rectangles
19// may be drawn overlapping. However, if we use bb1.UR.x+1 == bb2.LL.x
20// there may or may not be a gap between them, even in the same device
21// depending on their positions. When CELLSPACING > 1, this isn't as much
22// of a problem.
23//
24// We allow negative spacing as a hack to allow overlapping cell boundaries.
25// For the reasons discussed above, this is difficult to get correct.
26// This is an important enough case we should extend the table model to
27// support it correctly. This could be done by allowing a table attribute,
28// e.g., CELLGRID=n, which sets CELLBORDER=0 and has the border drawing
29// handled correctly by the table.
30
31#include "config.h"
32
33#include <assert.h>
34#include <cdt/cdt.h>
35#include <common/htmltable.h>
36#include <common/pointset.h>
37#include <common/render.h>
38#include <float.h>
39#include <inttypes.h>
40#include <limits.h>
41#include <math.h>
42#include <stdbool.h>
43#include <stddef.h>
44#include <stdint.h>
45#include <stdio.h>
46#include <util/agxbuf.h>
47#include <util/alloc.h>
48#include <util/bitarray.h>
49#include <util/exit.h>
50#include <util/gv_math.h>
51#include <util/prisize_t.h>
52#include <util/strcasecmp.h>
53#include <util/streq.h>
54#include <util/unreachable.h>
55
56#define DEFAULT_BORDER 1
57#define DEFAULT_CELLPADDING 2
58#define DEFAULT_CELLSPACING 2
59
60typedef struct {
61 char *url;
62 char *tooltip;
63 char *target;
64 char *id;
69
70#ifdef DEBUG
71static void printCell(htmlcell_t *cp, int ind);
72#endif
73
74/* Replace current font attributes in env with ones from fp,
75 * storing old attributes in savp. We only deal with attributes
76 * set in env. The attributes are restored via popFontInfo.
77 */
78static void pushFontInfo(htmlenv_t *env, textfont_t *fp, textfont_t *savp) {
79 if (env->finfo.name) {
80 if (fp->name) {
81 savp->name = env->finfo.name;
82 env->finfo.name = fp->name;
83 } else
84 savp->name = NULL;
85 }
86 if (env->finfo.color) {
87 if (fp->color) {
88 savp->color = env->finfo.color;
89 env->finfo.color = fp->color;
90 } else
91 savp->color = NULL;
92 }
93 if (env->finfo.size >= 0) {
94 if (fp->size >= 0) {
95 savp->size = env->finfo.size;
96 env->finfo.size = fp->size;
97 } else
98 savp->size = -1.0;
99 }
100}
101
102/* Restore saved font attributes.
103 * Copy only set values.
104 */
105static void popFontInfo(htmlenv_t *env, textfont_t *savp) {
106 if (savp->name)
107 env->finfo.name = savp->name;
108 if (savp->color)
109 env->finfo.color = savp->color;
110 if (savp->size >= 0.0)
111 env->finfo.size = savp->size;
112}
113
114static void emit_htextspans(GVJ_t *job, size_t nspans, htextspan_t *spans,
115 pointf p, double halfwidth_x, textfont_t finfo,
116 boxf b, int simple) {
117 double center_x, left_x, right_x;
118 textspan_t tl;
119 textfont_t tf;
120 pointf p_ = {0.0, 0.0};
121 textspan_t *ti;
122
123 center_x = p.x;
124 left_x = center_x - halfwidth_x;
125 right_x = center_x + halfwidth_x;
126
127 /* Initial p is in center of text block; set initial baseline
128 * to top of text block.
129 */
130 p_.y = p.y + (b.UR.y - b.LL.y) / 2.0;
131
133 for (size_t i = 0; i < nspans; i++) {
134 /* set p.x to leftmost point where the line of text begins */
135 switch (spans[i].just) {
136 case 'l':
137 p.x = left_x;
138 break;
139 case 'r':
140 p.x = right_x - spans[i].size;
141 break;
142 default:
143 case 'n':
144 p.x = center_x - spans[i].size / 2.0;
145 break;
146 }
147 p_.y -= spans[i].lfsize; /* move to current base line */
148
149 ti = spans[i].items;
150 for (size_t j = 0; j < spans[i].nitems; j++) {
151 if (ti->font && ti->font->size > 0)
152 tf.size = ti->font->size;
153 else
154 tf.size = finfo.size;
155 if (ti->font && ti->font->name)
156 tf.name = ti->font->name;
157 else
158 tf.name = finfo.name;
159 if (ti->font && ti->font->color)
160 tf.color = ti->font->color;
161 else
162 tf.color = finfo.color;
163 if (ti->font && ti->font->flags)
164 tf.flags = ti->font->flags;
165 else
166 tf.flags = 0;
167
169
170 tl.str = ti->str;
171 tl.font = &tf;
173 if (simple)
175 else
176 tl.yoffset_centerline = 1;
178 tl.layout = ti->layout;
179 tl.size.x = ti->size.x;
180 tl.size.y = spans[i].lfsize;
181 tl.just = 'l';
182
183 p_.x = p.x;
184 gvrender_textspan(job, p_, &tl);
185 p.x += ti->size.x;
186 ti++;
187 }
188 }
189
191}
192
193static void emit_html_txt(GVJ_t *job, htmltxt_t *tp, htmlenv_t *env) {
194 double halfwidth_x;
195 pointf p;
196
197 /* make sure that there is something to do */
198 if (tp->nspans < 1)
199 return;
200
201 halfwidth_x = (tp->box.UR.x - tp->box.LL.x) / 2.0;
202 p.x = env->pos.x + (tp->box.UR.x + tp->box.LL.x) / 2.0;
203 p.y = env->pos.y + (tp->box.UR.y + tp->box.LL.y) / 2.0;
204
205 emit_htextspans(job, tp->nspans, tp->spans, p, halfwidth_x, env->finfo,
206 tp->box, tp->simple);
207}
208
209static void doSide(GVJ_t *job, pointf p, double wd, double ht) {
210 boxf BF;
211
212 BF.LL = p;
213 BF.UR.x = p.x + wd;
214 BF.UR.y = p.y + ht;
215 gvrender_box(job, BF, 1);
216}
217
218/* Convert boxf into four corner points
219 * If border is > 1, inset the points by half the border.
220 * It is assumed AF is pointf[4], so the data is store there
221 * and AF is returned.
222 */
223static pointf *mkPts(pointf *AF, boxf b, int border) {
224 AF[0] = b.LL;
225 AF[2] = b.UR;
226 if (border > 1) {
227 const double delta = border / 2.0;
228 AF[0].x += delta;
229 AF[0].y += delta;
230 AF[2].x -= delta;
231 AF[2].y -= delta;
232 }
233 AF[1].x = AF[2].x;
234 AF[1].y = AF[0].y;
235 AF[3].x = AF[0].x;
236 AF[3].y = AF[2].y;
237
238 return AF;
239}
240
241/* Draw a rectangular border for the box b.
242 * Handles dashed and dotted styles, rounded corners.
243 * Also handles thick lines.
244 * Assume dp->border > 0
245 */
246static void doBorder(GVJ_t *job, htmldata_t *dp, boxf b) {
247 pointf AF[7];
248 char *sptr[2];
249 char *color = dp->pencolor ? dp->pencolor : DEFAULT_COLOR;
250 unsigned short sides;
251
253 if (dp->style.dashed || dp->style.dotted) {
254 sptr[0] = sptr[1] = NULL;
255 if (dp->style.dashed)
256 sptr[0] = "dashed";
257 else if (dp->style.dotted)
258 sptr[0] = "dotted";
259 gvrender_set_style(job, sptr);
260 } else
263
264 if (dp->style.rounded)
265 round_corners(job, mkPts(AF, b, dp->border), 4,
266 (graphviz_polygon_style_t){.rounded = true}, 0);
267 else if ((sides = (dp->flags & BORDER_MASK))) {
268 mkPts(AF + 1, b, dp->border); /* AF[1-4] has LL=SW,SE,UR=NE,NW */
269 switch (sides) {
270 case BORDER_BOTTOM:
271 gvrender_polyline(job, AF + 1, 2);
272 break;
273 case BORDER_RIGHT:
274 gvrender_polyline(job, AF + 2, 2);
275 break;
276 case BORDER_TOP:
277 gvrender_polyline(job, AF + 3, 2);
278 break;
279 case BORDER_LEFT:
280 AF[0] = AF[4];
281 gvrender_polyline(job, AF, 2);
282 break;
284 gvrender_polyline(job, AF + 1, 3);
285 break;
287 gvrender_polyline(job, AF + 2, 3);
288 break;
289 case BORDER_TOP | BORDER_LEFT:
290 AF[5] = AF[1];
291 gvrender_polyline(job, AF + 3, 3);
292 break;
294 AF[0] = AF[4];
295 gvrender_polyline(job, AF, 3);
296 break;
298 gvrender_polyline(job, AF + 1, 4);
299 break;
301 AF[5] = AF[1];
302 gvrender_polyline(job, AF + 2, 4);
303 break;
305 AF[5] = AF[1];
306 AF[6] = AF[2];
307 gvrender_polyline(job, AF + 3, 4);
308 break;
310 AF[0] = AF[4];
311 gvrender_polyline(job, AF, 4);
312 break;
314 gvrender_polyline(job, AF + 1, 2);
315 gvrender_polyline(job, AF + 3, 2);
316 break;
318 AF[0] = AF[4];
319 gvrender_polyline(job, AF, 2);
320 gvrender_polyline(job, AF + 2, 2);
321 break;
322 default:
323 break;
324 }
325 } else {
326 if (dp->border > 1) {
327 const double delta = dp->border / 2.0;
328 b.LL.x += delta;
329 b.LL.y += delta;
330 b.UR.x -= delta;
331 b.UR.y -= delta;
332 }
333 gvrender_box(job, b, 0);
334 }
335}
336
337/* Set up fill values from given color; make pen transparent.
338 * Return type of fill required.
339 */
340static int setFill(GVJ_t *job, char *color, int angle, htmlstyle_t style,
341 char *clrs[2]) {
342 int filled;
343 double frac;
344 if (findStopColor(color, clrs, &frac)) {
345 gvrender_set_fillcolor(job, clrs[0]);
346 if (clrs[1])
347 gvrender_set_gradient_vals(job, clrs[1], angle, frac);
348 else
349 gvrender_set_gradient_vals(job, DEFAULT_COLOR, angle, frac);
350 if (style.radial)
351 filled = RGRADIENT;
352 else
353 filled = GRADIENT;
354 } else {
356 filled = FILL;
357 }
358 gvrender_set_pencolor(job, "transparent");
359 return filled;
360}
361
362/* Save current map values.
363 * Initialize fields in job->obj pertaining to anchors.
364 * In particular, this also sets the output rectangle.
365 * If there is something to do,
366 * start the anchor and returns 1.
367 * Otherwise, it returns 0.
368 *
369 * FIX: Should we provide a tooltip if none is set, as is done
370 * for nodes, edges, etc. ?
371 */
372static int initAnchor(GVJ_t *job, htmlenv_t *env, htmldata_t *data, boxf b,
373 htmlmap_data_t *save) {
374 obj_state_t *obj = job->obj;
375 char *id;
376 static int anchorId;
377 agxbuf xb = {0};
378
379 save->url = obj->url;
380 save->tooltip = obj->tooltip;
381 save->target = obj->target;
382 save->id = obj->id;
383 save->explicit_tooltip = obj->explicit_tooltip != 0;
384 id = data->id;
385 if (!id || !*id) { /* no external id, so use the internal one */
386 if (!env->objid) {
387 env->objid = gv_strdup(getObjId(job, obj->u.n, &xb));
388 env->objid_set = true;
389 }
390 agxbprint(&xb, "%s_%d", env->objid, anchorId++);
391 id = agxbuse(&xb);
392 }
393 const bool changed = initMapData(job, NULL, data->href, data->title,
394 data->target, id, obj->u.g);
395 agxbfree(&xb);
396
397 if (changed) {
398 if (obj->url || obj->explicit_tooltip) {
399 emit_map_rect(job, b);
400 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
401 }
402 }
403 return changed;
404}
405
406#define RESET(fld) \
407 if (obj->fld != save->fld) { \
408 free(obj->fld); \
409 obj->fld = save->fld; \
410 }
411
412/* Pop context pushed by initAnchor.
413 * This is done by ending current anchor, restoring old values and
414 * freeing new.
415 *
416 * NB: We don't save or restore geometric map info. This is because
417 * this preservation of map context is only necessary for SVG-like
418 * systems where graphical items are wrapped in an anchor, and we map
419 * top-down. For ordinary map anchors, this is all done bottom-up, so
420 * the geometric map info at the higher level hasn't been emitted yet.
421 */
422static void endAnchor(GVJ_t *job, htmlmap_data_t *save) {
423 obj_state_t *obj = job->obj;
424
425 if (obj->url || obj->explicit_tooltip)
427 RESET(url);
428 RESET(tooltip);
429 RESET(target);
430 RESET(id);
432}
433
434static void emit_html_cell(GVJ_t *job, htmlcell_t *cp, htmlenv_t *env);
435
436/* place vertical and horizontal lines between adjacent cells and
437 * extend the lines to intersect the rounded table boundary
438 */
439static void emit_html_rules(GVJ_t *job, htmlcell_t *cp, htmlenv_t *env,
440 char *color, htmlcell_t *nextc) {
441 pointf rule_pt;
442 double rule_length;
443 double base;
444 pointf pos = env->pos;
445
446 if (!color)
450
451 boxf pts = cp->data.box;
452 pts.LL.x += pos.x;
453 pts.UR.x += pos.x;
454 pts.LL.y += pos.y;
455 pts.UR.y += pos.y;
456
457 // Determine vertical line coordinate and length
458 if (cp->vruled && cp->col + cp->colspan < cp->parent->column_count) {
459 if (cp->row == 0) { // first row
460 // extend to center of table border and add half cell spacing
461 base = cp->parent->data.border + cp->parent->data.space / 2;
462 rule_pt.y = pts.LL.y - cp->parent->data.space / 2;
463 } else if (cp->row + cp->rowspan == cp->parent->row_count) { // bottom row
464 // extend to center of table border and add half cell spacing
465 base = cp->parent->data.border + cp->parent->data.space / 2;
466 rule_pt.y = pts.LL.y - cp->parent->data.space / 2 - base;
467 } else {
468 base = 0;
469 rule_pt.y = pts.LL.y - cp->parent->data.space / 2;
470 }
471 rule_pt.x = pts.UR.x + cp->parent->data.space / 2;
472 rule_length = base + pts.UR.y - pts.LL.y + cp->parent->data.space;
473 doSide(job, rule_pt, 0, rule_length);
474 }
475 // Determine the horizontal coordinate and length
476 if (cp->hruled && cp->row + cp->rowspan < cp->parent->row_count) {
477 if (cp->col == 0) { // first column
478 // extend to center of table border and add half cell spacing
479 base = cp->parent->data.border + cp->parent->data.space / 2;
480 rule_pt.x = pts.LL.x - base - cp->parent->data.space / 2;
481 if (cp->col + cp->colspan == cp->parent->column_count) // also last column
482 base *= 2;
483 /* incomplete row of cells; extend line to end */
484 else if (nextc && nextc->row != cp->row) {
485 base += cp->parent->data.box.UR.x + pos.x -
486 (pts.UR.x + cp->parent->data.space / 2);
487 }
488 } else if (cp->col + cp->colspan ==
489 cp->parent->column_count) { // last column
490 // extend to center of table border and add half cell spacing
491 base = cp->parent->data.border + cp->parent->data.space / 2;
492 rule_pt.x = pts.LL.x - cp->parent->data.space / 2;
493 } else {
494 base = 0;
495 rule_pt.x = pts.LL.x - cp->parent->data.space / 2;
496 /* incomplete row of cells; extend line to end */
497 if (nextc && nextc->row != cp->row) {
498 base += cp->parent->data.box.UR.x + pos.x -
499 (pts.UR.x + cp->parent->data.space / 2);
500 }
501 }
502 rule_pt.y = pts.LL.y - cp->parent->data.space / 2;
503 rule_length = base + pts.UR.x - pts.LL.x + cp->parent->data.space;
504 doSide(job, rule_pt, rule_length, 0);
505 }
506}
507
508static void emit_html_tbl(GVJ_t *job, htmltbl_t *tbl, htmlenv_t *env) {
509 boxf pts = tbl->data.box;
510 pointf pos = env->pos;
511 htmlcell_t *cp;
512 static textfont_t savef;
513 htmlmap_data_t saved;
514 int anchor; /* if true, we need to undo anchor settings. */
515 const bool doAnchor = tbl->data.href || tbl->data.target || tbl->data.title;
516 pointf AF[4];
517
518 if (tbl->font)
519 pushFontInfo(env, tbl->font, &savef);
520
521 pts.LL.x += pos.x;
522 pts.UR.x += pos.x;
523 pts.LL.y += pos.y;
524 pts.UR.y += pos.y;
525
526 if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST))
527 anchor = initAnchor(job, env, &tbl->data, pts, &saved);
528 else
529 anchor = 0;
530
531 if (!tbl->data.style.invisible) {
532
533 /* Fill first */
534 if (tbl->data.bgcolor) {
535 char *clrs[2] = {0};
536 int filled = setFill(job, tbl->data.bgcolor, tbl->data.gradientangle,
537 tbl->data.style, clrs);
538 if (tbl->data.style.rounded) {
539 round_corners(job, mkPts(AF, pts, tbl->data.border), 4,
540 (graphviz_polygon_style_t){.rounded = true}, filled);
541 } else
542 gvrender_box(job, pts, filled);
543 free(clrs[0]);
544 free(clrs[1]);
545 }
546
547 /* Draw table rules and border.
548 * Draw before emitting cell contents so text is always painted above
549 * table-level shapes in output formats that use source order for z-order.
550 * At present, we set the penwidth to 1 for rules until we provide the
551 * calculations to take into account wider rules.
552 */
553 gvrender_set_penwidth(job, 1.0);
554 for (htmlcell_t **cells = tbl->cells; (cp = *cells++);) {
555 if (cp->hruled || cp->vruled)
556 emit_html_rules(job, cp, env, tbl->data.pencolor, *cells);
557 }
558
559 if (tbl->data.border)
560 doBorder(job, &tbl->data, pts);
561
562 for (htmlcell_t **cells = tbl->cells; *cells; ++cells) {
563 emit_html_cell(job, *cells, env);
564 }
565 }
566
567 if (anchor)
568 endAnchor(job, &saved);
569
570 if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) {
571 if (initAnchor(job, env, &tbl->data, pts, &saved))
572 endAnchor(job, &saved);
573 }
574
575 if (tbl->font)
576 popFontInfo(env, &savef);
577}
578
579/* The image will be centered in the given box.
580 * Scaling is determined by either the image's scale attribute,
581 * or the imagescale attribute of the graph object being drawn.
582 */
583static void emit_html_img(GVJ_t *job, htmlimg_t *cp, htmlenv_t *env) {
584 pointf A[4];
585 boxf bb = cp->box;
586 char *scale;
587
588 bb.LL.x += env->pos.x;
589 bb.LL.y += env->pos.y;
590 bb.UR.x += env->pos.x;
591 bb.UR.y += env->pos.y;
592
593 A[0] = bb.UR;
594 A[2] = bb.LL;
595 A[1].x = A[2].x;
596 A[1].y = A[0].y;
597 A[3].x = A[0].x;
598 A[3].y = A[2].y;
599
600 if (cp->scale)
601 scale = cp->scale;
602 else
603 scale = env->imgscale;
604 assert(cp->src);
605 assert(cp->src[0]);
606 gvrender_usershape(job, cp->src, A, 4, true, scale, "mc");
607}
608
609static void emit_html_cell(GVJ_t *job, htmlcell_t *cp, htmlenv_t *env) {
610 htmlmap_data_t saved;
611 boxf pts = cp->data.box;
612 pointf pos = env->pos;
613 int inAnchor;
614 const bool doAnchor = cp->data.href || cp->data.target || cp->data.title;
615 pointf AF[4];
616
617 pts.LL.x += pos.x;
618 pts.UR.x += pos.x;
619 pts.LL.y += pos.y;
620 pts.UR.y += pos.y;
621
622 if (doAnchor && !(job->flags & EMIT_CLUSTERS_LAST))
623 inAnchor = initAnchor(job, env, &cp->data, pts, &saved);
624 else
625 inAnchor = 0;
626
627 if (!cp->data.style.invisible) {
628 if (cp->data.bgcolor) {
629 char *clrs[2];
630 int filled = setFill(job, cp->data.bgcolor, cp->data.gradientangle,
631 cp->data.style, clrs);
632 if (cp->data.style.rounded) {
633 round_corners(job, mkPts(AF, pts, cp->data.border), 4,
634 (graphviz_polygon_style_t){.rounded = true}, filled);
635 } else
636 gvrender_box(job, pts, filled);
637 free(clrs[0]);
638 }
639
640 if (cp->data.border)
641 doBorder(job, &cp->data, pts);
642
643 if (cp->child.kind == HTML_TBL)
644 emit_html_tbl(job, cp->child.u.tbl, env);
645 else if (cp->child.kind == HTML_IMAGE)
646 emit_html_img(job, cp->child.u.img, env);
647 else
648 emit_html_txt(job, cp->child.u.txt, env);
649 }
650
651 if (inAnchor)
652 endAnchor(job, &saved);
653
654 if (doAnchor && (job->flags & EMIT_CLUSTERS_LAST)) {
655 if (initAnchor(job, env, &cp->data, pts, &saved))
656 endAnchor(job, &saved);
657 }
658}
659
660/* Push new obj on stack to be used in common by all
661 * html elements with anchors.
662 * This inherits the type, emit_state, and object of the
663 * parent, as well as the url, explicit, target and tooltip.
664 */
665static void allocObj(GVJ_t *job) {
666 obj_state_t *obj;
668
669 obj = push_obj_state(job);
670 parent = obj->parent;
671 obj->type = parent->type;
672 obj->emit_state = parent->emit_state;
673 switch (obj->type) {
674 case NODE_OBJTYPE:
675 obj->u.n = parent->u.n;
676 break;
678 obj->u.g = parent->u.g;
679 break;
680 case CLUSTER_OBJTYPE:
681 obj->u.sg = parent->u.sg;
682 break;
683 case EDGE_OBJTYPE:
684 obj->u.e = parent->u.e;
685 break;
686 default:
687 UNREACHABLE();
688 }
689 obj->url = parent->url;
690 obj->tooltip = parent->tooltip;
691 obj->target = parent->target;
692 obj->explicit_tooltip = parent->explicit_tooltip;
693}
694
695static void freeObj(GVJ_t *job) {
696 obj_state_t *obj = job->obj;
697
698 obj->url = NULL;
699 obj->tooltip = NULL;
700 obj->target = NULL;
701 obj->id = NULL;
702 pop_obj_state(job);
703}
704
705static double heightOfLbl(htmllabel_t *lp) {
706 double sz = 0.0;
707
708 switch (lp->kind) {
709 case HTML_TBL:
710 sz = lp->u.tbl->data.box.UR.y - lp->u.tbl->data.box.LL.y;
711 break;
712 case HTML_IMAGE:
713 sz = lp->u.img->box.UR.y - lp->u.img->box.LL.y;
714 break;
715 case HTML_TEXT:
716 sz = lp->u.txt->box.UR.y - lp->u.txt->box.LL.y;
717 break;
718 default:
719 UNREACHABLE();
720 }
721 return sz;
722}
723
725 htmlenv_t env;
726 pointf p;
727
728 allocObj(job);
729
730 p = tp->pos;
731 switch (tp->valign) {
732 case 't':
733 p.y = tp->pos.y + (tp->space.y - heightOfLbl(lp)) / 2.0 - 1;
734 break;
735 case 'b':
736 p.y = tp->pos.y - (tp->space.y - heightOfLbl(lp)) / 2.0 - 1;
737 break;
738 default:
739 /* no-op */
740 break;
741 }
742 env.pos = p;
743 env.finfo.color = tp->fontcolor;
744 env.finfo.name = tp->fontname;
745 env.finfo.size = tp->fontsize;
746 env.imgscale = agget(job->obj->u.n, "imagescale");
747 env.objid = job->obj->id;
748 env.objid_set = false;
749 if (env.imgscale == NULL || env.imgscale[0] == '\0')
750 env.imgscale = "false";
751 if (lp->kind == HTML_TBL) {
752 htmltbl_t *tbl = lp->u.tbl;
753
754 /* set basic graphics context */
755 /* Need to override line style set by node. */
757 if (tbl->data.pencolor)
759 else
761 emit_html_tbl(job, tbl, &env);
762 } else {
763 emit_html_txt(job, lp->u.txt, &env);
764 }
765 if (env.objid_set)
766 free(env.objid);
767 freeObj(job);
768}
769
771 free(dp->href);
772 free(dp->port);
773 free(dp->target);
774 free(dp->id);
775 free(dp->title);
776 free(dp->bgcolor);
777 free(dp->pencolor);
778}
779
781 if (!t)
782 return;
783
784 htextspan_t *const tl = t->spans;
785 for (size_t i = 0; i < t->nspans; i++) {
786 textspan_t *const ti = tl[i].items;
787 for (size_t j = 0; j < tl[i].nitems; j++) {
788 free(ti[j].str);
789 if (ti[j].layout && ti[j].free_layout)
790 ti[j].free_layout(ti[j].layout);
791 }
792 free(ti);
793 }
794 free(tl);
795 free(t);
796}
797
798static void free_html_img(htmlimg_t *ip) {
799 free(ip->src);
800 free(ip);
801}
802
803static void free_html_cell(htmlcell_t *cp) {
804 free_html_label(&cp->child, 0);
805 free_html_data(&cp->data);
806 free(cp);
807}
808
809/* If tbl->row_count is `SIZE_MAX`, table is in initial state from
810 * HTML parse, with data stored in `tbl->prev` and `tbl->rows`. Once run through
811 * processTbl, data is stored in `tbl->parent` and `tbl->cells`, and
812 * tbl->row_count is < `SIZE_MAX`.
813 */
814static void free_html_tbl(htmltbl_t *tbl) {
815 htmlcell_t **cells;
816
817 if (tbl->row_count == SIZE_MAX) { // raw, parsed table
818 LIST_FREE(&tbl->rows);
819 } else {
820 cells = tbl->cells;
821
822 free(tbl->heights);
823 free(tbl->widths);
824 while (*cells) {
825 free_html_cell(*cells);
826 cells++;
827 }
828 free(tbl->cells);
829 }
830 free_html_data(&tbl->data);
831 free(tbl);
832}
833
834void free_html_label(htmllabel_t *lp, int root) {
835 if (lp->kind == HTML_TBL)
836 free_html_tbl(lp->u.tbl);
837 else if (lp->kind == HTML_IMAGE)
838 free_html_img(lp->u.img);
839 else
840 free_html_text(lp->u.txt);
841 if (root)
842 free(lp);
843}
844
845static htmldata_t *portToTbl(htmltbl_t *, char *);
846
847static htmldata_t *portToCell(htmlcell_t *cp, char *id) {
848 htmldata_t *rv;
849
850 if (cp->data.port && strcasecmp(cp->data.port, id) == 0)
851 rv = &cp->data;
852 else if (cp->child.kind == HTML_TBL)
853 rv = portToTbl(cp->child.u.tbl, id);
854 else
855 rv = NULL;
856
857 return rv;
858}
859
860/* See if tp or any of its child cells has the given port id.
861 * If true, return corresponding box.
862 */
863static htmldata_t *portToTbl(htmltbl_t *tp, char *id) {
864 htmldata_t *rv;
865 htmlcell_t **cells;
866 htmlcell_t *cp;
867
868 if (tp->data.port && strcasecmp(tp->data.port, id) == 0)
869 rv = &tp->data;
870 else {
871 rv = NULL;
872 cells = tp->cells;
873 while ((cp = *cells++)) {
874 if ((rv = portToCell(cp, id)))
875 break;
876 }
877 }
878
879 return rv;
880}
881
882/* See if edge port corresponds to part of the html node.
883 * If successful, return pointer to port's box.
884 * Else return NULL.
885 */
886boxf *html_port(node_t *n, char *pname, unsigned char *sides) {
887 assert(pname != NULL && !streq(pname, ""));
888 htmldata_t *tp;
889 htmllabel_t *lbl = ND_label(n)->u.html;
890 boxf *rv = NULL;
891
892 if (lbl->kind == HTML_TEXT)
893 return NULL;
894
895 tp = portToTbl(lbl->u.tbl, pname);
896 if (tp) {
897 rv = &tp->box;
898 *sides = tp->sides;
899 }
900 return rv;
901}
902
903static int size_html_txt(GVC_t *gvc, htmltxt_t *ftxt, htmlenv_t *env) {
904 double xsize = 0.0; /* width of text block */
905 double ysize = 0.0; /* height of text block */
906 double lsize; /* height of current line */
907 double mxfsize = 0.0; /* max. font size for the current line */
908 double curbline = 0.0; /* dist. of current base line from top */
909 pointf sz;
910 double width;
911 textspan_t lp;
912 textfont_t tf = {NULL, NULL, NULL, 0.0, 0, 0};
913 double maxoffset, maxlayout, mxysize = 0.0;
914 bool simple = true; // one item per span, same font size/face, no flags
915 double prev_fsize = -1;
916 char *prev_fname = NULL;
917
918 for (size_t i = 0; i < ftxt->nspans; i++) {
919 if (ftxt->spans[i].nitems > 1) {
920 simple = false;
921 break;
922 }
923 if (ftxt->spans[i].items[0].font) {
924 if (ftxt->spans[i].items[0].font->flags) {
925 simple = false;
926 break;
927 }
928 if (ftxt->spans[i].items[0].font->size > 0)
929 tf.size = ftxt->spans[i].items[0].font->size;
930 else
931 tf.size = env->finfo.size;
932 if (ftxt->spans[i].items[0].font->name)
933 tf.name = ftxt->spans[i].items[0].font->name;
934 else
935 tf.name = env->finfo.name;
936 } else {
937 tf.size = env->finfo.size;
938 tf.name = env->finfo.name;
939 }
940 if (i == 0)
941 prev_fsize = tf.size;
942 else if (tf.size != prev_fsize) {
943 simple = false;
944 break;
945 }
946 if (prev_fname == NULL)
947 prev_fname = tf.name;
948 else if (strcmp(tf.name, prev_fname)) {
949 simple = false;
950 break;
951 }
952 }
953 ftxt->simple = simple;
954
955 for (size_t i = 0; i < ftxt->nspans; i++) {
956 width = 0;
957 mxysize = maxoffset = maxlayout = mxfsize = 0;
958 for (size_t j = 0; j < ftxt->spans[i].nitems; j++) {
959 lp.str = strdup_and_subst_obj(ftxt->spans[i].items[j].str, env->obj);
960 if (ftxt->spans[i].items[j].font) {
961 if (ftxt->spans[i].items[j].font->flags)
962 tf.flags = ftxt->spans[i].items[j].font->flags;
963 else if (env->finfo.flags > 0)
964 tf.flags = env->finfo.flags;
965 else
966 tf.flags = 0;
967 if (ftxt->spans[i].items[j].font->size > 0)
968 tf.size = ftxt->spans[i].items[j].font->size;
969 else
970 tf.size = env->finfo.size;
971 if (ftxt->spans[i].items[j].font->name)
972 tf.name = ftxt->spans[i].items[j].font->name;
973 else
974 tf.name = env->finfo.name;
975 if (ftxt->spans[i].items[j].font->color)
976 tf.color = ftxt->spans[i].items[j].font->color;
977 else
978 tf.color = env->finfo.color;
979 } else {
980 tf.size = env->finfo.size;
981 tf.name = env->finfo.name;
982 tf.color = env->finfo.color;
983 tf.flags = env->finfo.flags;
984 }
985 lp.font = dtinsert(gvc->textfont_dt, &tf);
986 sz = textspan_size(gvc, &lp);
987 free(ftxt->spans[i].items[j].str);
988 ftxt->spans[i].items[j].str = lp.str;
989 ftxt->spans[i].items[j].size.x = sz.x;
990 ftxt->spans[i].items[j].yoffset_layout = lp.yoffset_layout;
992 ftxt->spans[i].items[j].font = lp.font;
993 ftxt->spans[i].items[j].layout = lp.layout;
994 ftxt->spans[i].items[j].free_layout = lp.free_layout;
995 width += sz.x;
996 mxfsize = MAX(tf.size, mxfsize);
997 mxysize = MAX(sz.y, mxysize);
998 maxoffset = MAX(lp.yoffset_centerline, maxoffset);
999 maxlayout = MAX(lp.yoffset_layout, maxlayout);
1000 }
1001 /* lsize = mxfsize * LINESPACING; */
1002 ftxt->spans[i].size = width;
1003 /* ysize - curbline is the distance from the previous
1004 * baseline to the bottom of the previous line.
1005 * Then, in the current line, we set the baseline to
1006 * be 5/6 of the max. font size. Thus, lfsize gives the
1007 * distance from the previous baseline to the new one.
1008 */
1009 /* ftxt->spans[i].lfsize = 5*mxfsize/6 + ysize - curbline; */
1010 if (simple) {
1011 lsize = mxysize;
1012 if (i == 0)
1013 /* Place baseline using actual font metrics: centers the pango
1014 * logical rectangle in the cell rather than approximating with
1015 * raw font size. maxlayout = baseline distance from box top
1016 * (yoffset_layout from the text layout engine); maxoffset =
1017 * yoffset_centerline (small above-baseline correction applied
1018 * by the renderer to convert from rendering origin to baseline). */
1019 ftxt->spans[i].lfsize = maxlayout + maxoffset;
1020 else
1021 ftxt->spans[i].lfsize = mxysize;
1022 } else {
1023 lsize = mxfsize;
1024 if (i == 0)
1025 ftxt->spans[i].lfsize = mxfsize - maxoffset;
1026 else
1027 ftxt->spans[i].lfsize = mxfsize + ysize - curbline - maxoffset;
1028 }
1029 curbline += ftxt->spans[i].lfsize;
1030 xsize = MAX(width, xsize);
1031 ysize += lsize;
1032 }
1033 ftxt->box.UR.x = xsize;
1034 if (ftxt->nspans == 1)
1035 ftxt->box.UR.y = mxysize;
1036 else
1037 ftxt->box.UR.y = ysize;
1038 return 0;
1039}
1040
1041static int size_html_tbl(graph_t *g, htmltbl_t *tbl, htmlcell_t *parent,
1042 htmlenv_t *env);
1043
1044static int size_html_img(htmlimg_t *img, htmlenv_t *env) {
1045 box b;
1046 int rv;
1047
1048 b.LL.x = b.LL.y = 0;
1049 b.UR = gvusershape_size(env->g, img->src);
1050 if (b.UR.x == -1 && b.UR.y == -1) {
1051 rv = 1;
1052 b.UR.x = b.UR.y = 0;
1053 agerrorf("No or improper image file=\"%s\"\n", img->src);
1054 } else {
1055 rv = 0;
1056 GD_has_images(env->g) = true;
1057 }
1058
1059 B2BF(b, img->box);
1060 return rv;
1061}
1062
1064 htmlenv_t *env) {
1065 int rv;
1066 pointf sz, child_sz;
1067 int margin;
1068
1069 cp->parent = parent;
1070 if (!(cp->data.flags & PAD_SET)) {
1071 if (parent->data.flags & PAD_SET)
1072 cp->data.pad = parent->data.pad;
1073 else
1075 }
1076 if (!(cp->data.flags & BORDER_SET)) {
1077 if (parent->cellborder >= 0)
1078 cp->data.border = (unsigned char)parent->cellborder;
1079 else if (parent->data.flags & BORDER_SET)
1080 cp->data.border = parent->data.border;
1081 else
1083 }
1084
1085 if (cp->child.kind == HTML_TBL) {
1086 rv = size_html_tbl(g, cp->child.u.tbl, cp, env);
1087 child_sz = cp->child.u.tbl->data.box.UR;
1088 } else if (cp->child.kind == HTML_IMAGE) {
1089 rv = size_html_img(cp->child.u.img, env);
1090 child_sz = cp->child.u.img->box.UR;
1091 } else {
1092 rv = size_html_txt(GD_gvc(g), cp->child.u.txt, env);
1093 child_sz = cp->child.u.txt->box.UR;
1094 }
1095
1096 margin = 2 * (cp->data.pad + cp->data.border);
1097 sz.x = child_sz.x + margin;
1098 sz.y = child_sz.y + margin;
1099
1100 if (cp->data.flags & FIXED_FLAG) {
1101 if (cp->data.width && cp->data.height) {
1102 if ((cp->data.width < sz.x || cp->data.height < sz.y) &&
1103 cp->child.kind != HTML_IMAGE) {
1104 agwarningf("cell size too small for content\n");
1105 rv = 1;
1106 }
1107 sz.x = sz.y = 0;
1108
1109 } else {
1110 agwarningf("fixed cell size with unspecified width or height\n");
1111 rv = 1;
1112 }
1113 }
1114 cp->data.box.UR.x = MAX(sz.x, cp->data.width);
1115 cp->data.box.UR.y = MAX(sz.y, cp->data.height);
1116 return rv;
1117}
1118
1119static uint16_t findCol(PointSet *ps, int row, int col, htmlcell_t *cellp) {
1120 int notFound = 1;
1121 int lastc;
1122 int i, j, c;
1123 int end = cellp->colspan - 1;
1124
1125 while (notFound) {
1126 lastc = col + end;
1127 for (c = lastc; c >= col; c--) {
1128 if (isInPS(ps, c, row))
1129 break;
1130 }
1131 if (c >= col) /* conflict : try column after */
1132 col = c + 1;
1133 else
1134 notFound = 0;
1135 }
1136 for (j = col; j < col + cellp->colspan; j++) {
1137 for (i = row; i < row + cellp->rowspan; i++) {
1138 addPS(ps, j, i);
1139 }
1140 }
1141 assert(col >= 0 && col <= UINT16_MAX);
1142 return (uint16_t)col;
1143}
1144
1145/* Convert parser representation of cells into final form.
1146 * Find column and row positions of cells.
1147 * Recursively size cells.
1148 * Return 1 if problem sizing a cell.
1149 */
1150static int processTbl(graph_t *g, htmltbl_t *tbl, htmlenv_t *env) {
1151 htmlcell_t **cells;
1152 rows_t rows = tbl->rows;
1153 int rv = 0;
1154 size_t n_rows = 0;
1155 size_t n_cols = 0;
1156 PointSet *ps = newPS();
1157 bitarray_t is = bitarray_new((size_t)UINT16_MAX + 1);
1158
1159 size_t cnt = 0;
1160 for (uint16_t r = 0; r < LIST_SIZE(&rows); ++r) {
1161 row_t *rp = LIST_GET(&rows, r);
1162 cnt += LIST_SIZE(&rp->rp);
1163 if (rp->ruled) {
1164 bitarray_set(&is, r + 1, true);
1165 }
1166 }
1167
1168 cells = tbl->cells = gv_calloc(cnt + 1, sizeof(htmlcell_t *));
1169 for (uint16_t r = 0; r < LIST_SIZE(&rows); ++r) {
1170 row_t *rp = LIST_GET(&rows, r);
1171 uint16_t c = 0;
1172 for (size_t i = 0; i < LIST_SIZE(&rp->rp); ++i) {
1173 htmlcell_t *cellp = LIST_GET(&rp->rp, i);
1174 *cells++ = cellp;
1175 rv |= size_html_cell(g, cellp, tbl, env);
1176 c = findCol(ps, r, c, cellp);
1177 cellp->row = r;
1178 cellp->col = c;
1179 c += cellp->colspan;
1180 n_cols = MAX(c, n_cols);
1181 n_rows = MAX(r + cellp->rowspan, n_rows);
1182 if (bitarray_get(is, r + cellp->rowspan))
1183 cellp->hruled = true;
1184 }
1185 }
1186 tbl->row_count = n_rows;
1187 tbl->column_count = n_cols;
1188 LIST_FREE(&rows);
1189 bitarray_reset(&is);
1190 freePS(ps);
1191 return rv;
1192}
1193
1213
1214 // `processTbl` has already done step 1, “1. Calculate the minimum content
1215 // width (MCW) of each cell”, and stored it in `.data.box.UR.x`.
1216
1217 // Allocate space for minimum column widths. Note that we add an extra entry
1218 // to allow later code to make references like `table->widths[col + colspan]`.
1219 assert(table->widths == NULL && "table widths computed twice");
1220 table->widths = gv_calloc(table->column_count + 1, sizeof(double));
1221
1222 // “2. For each column, determine a … minimum column width from the cells that
1223 // span only that column. The minimum is that required by the cell with the
1224 // largest minimum cell width …”. Note that this loop and the following one
1225 // could be fused, but this would make the implementation harder to relate
1226 // back to the specification.
1227 for (htmlcell_t **i = table->cells; *i != NULL; ++i) {
1228 const htmlcell_t cell = **i;
1229 if (cell.colspan > 1) {
1230 continue;
1231 }
1232 assert(cell.col < table->column_count && "out of range cell");
1233 table->widths[cell.col] = fmax(table->widths[cell.col], cell.data.box.UR.x);
1234 }
1235
1236 // “3. For each cell that spans more than one column, increase the minimum
1237 // widths of the columns it spans so that together, they are at least as wide
1238 // as the cell. … If possible, widen all spanned columns by approximately the
1239 // same amount.”
1240 for (htmlcell_t **i = table->cells; *i != NULL; ++i) {
1241 const htmlcell_t cell = **i;
1242 if (cell.colspan == 1) {
1243 continue;
1244 }
1245
1246 // what is the current width of this cell’s column(s)’ span?
1247 double span_width = 0;
1248 assert(cell.col + cell.colspan <= table->column_count &&
1249 "cell spans wider than containing table");
1250 for (size_t j = 0; j < cell.colspan; ++j) {
1251 span_width += table->widths[cell.col + j];
1252 }
1253
1254 // if it is wider than its span (including cell spacing), widen the span
1255 const double spacing = (cell.colspan - 1) * table->data.space;
1256 if (span_width + spacing < cell.data.box.UR.x) {
1257 const double widen_by =
1258 (cell.data.box.UR.x - spacing - span_width) / cell.colspan;
1259 for (size_t j = 0; j < cell.colspan; ++j) {
1260 table->widths[cell.col + j] += widen_by;
1261 }
1262 }
1263 }
1264
1265 // take the minimum width for each column and apply it to its contained cells
1266 for (htmlcell_t **i = table->cells; *i != NULL; ++i) {
1267 htmlcell_t *cell = *i;
1268
1269 // what is the current width of this cell’s column(s)’ span?
1270 double min_width = 0;
1271 assert(cell->col + cell->colspan <= table->column_count &&
1272 "cell spans wider than containing table");
1273 for (size_t j = 0; j < cell->colspan; ++j) {
1274 min_width += table->widths[cell->col + j];
1275 }
1276
1277 // widen the cell if necessary
1278 const double spacing = (cell->colspan - 1) * table->data.space;
1279 cell->data.box.UR.x = fmax(cell->data.box.UR.x, min_width + spacing);
1280 }
1281}
1282
1289
1290 assert(table->heights == NULL && "table heights computed twice");
1291 table->heights = gv_calloc(table->row_count + 1, sizeof(double));
1292
1293 for (htmlcell_t **i = table->cells; *i != NULL; ++i) {
1294 const htmlcell_t cell = **i;
1295 if (cell.rowspan > 1) {
1296 continue;
1297 }
1298 assert(cell.row < table->row_count && "out of range cell");
1299 table->heights[cell.row] =
1300 fmax(table->heights[cell.row], cell.data.box.UR.y);
1301 }
1302
1303 for (htmlcell_t **i = table->cells; *i != NULL; ++i) {
1304 const htmlcell_t cell = **i;
1305 if (cell.rowspan == 1) {
1306 continue;
1307 }
1308
1309 double span_height = 0;
1310 assert(cell.row + cell.rowspan <= table->row_count &&
1311 "cell spans higher than containing table");
1312 for (size_t j = 0; j < cell.rowspan; ++j) {
1313 span_height += table->heights[cell.row + j];
1314 }
1315
1316 const double spacing = (cell.rowspan - 1) * table->data.space;
1317 if (span_height + spacing < cell.data.box.UR.y) {
1318 const double heighten_by =
1319 (cell.data.box.UR.y - spacing - span_height) / cell.rowspan;
1320 for (size_t j = 0; j < cell.rowspan; ++j) {
1321 table->heights[cell.row + j] += heighten_by;
1322 }
1323 }
1324 }
1325
1326 for (htmlcell_t **i = table->cells; *i != NULL; ++i) {
1327 htmlcell_t *cell = *i;
1328
1329 double min_height = 0;
1330 assert(cell->row + cell->rowspan <= table->row_count &&
1331 "cell spans higher than containing table");
1332 for (size_t j = 0; j < cell->rowspan; ++j) {
1333 min_height += table->heights[cell->row + j];
1334 }
1335
1336 const double spacing = (cell->rowspan - 1) * table->data.space;
1337 cell->data.box.UR.y = fmax(cell->data.box.UR.y, min_height + spacing);
1338 }
1339}
1340
1341static void pos_html_tbl(htmltbl_t *, boxf, unsigned char);
1342
1343/* Place image in cell
1344 * storing allowed space handed by parent cell.
1345 * How this space is used is handled in emit_html_img.
1346 */
1347static void pos_html_img(htmlimg_t *cp, boxf pos) { cp->box = pos; }
1348
1350static void pos_html_txt(htmltxt_t *ftxt, char c) {
1351 for (size_t i = 0; i < ftxt->nspans; i++) {
1352 if (ftxt->spans[i].just == UNSET_ALIGN) /* unset */
1353 ftxt->spans[i].just = c;
1354 }
1355}
1356
1357static void pos_html_cell(htmlcell_t *cp, boxf pos, unsigned char sides) {
1358 double delx, dely;
1359 pointf oldsz;
1360 boxf cbox;
1361
1362 if (!cp->data.pencolor && cp->parent->data.pencolor)
1364
1365 /* If fixed, align cell */
1366 if (cp->data.flags & FIXED_FLAG) {
1367 oldsz = cp->data.box.UR;
1368 delx = pos.UR.x - pos.LL.x - oldsz.x;
1369 if (delx > 0) {
1370 switch (cp->data.flags & HALIGN_MASK) {
1371 case HALIGN_LEFT:
1372 pos.UR.x = pos.LL.x + oldsz.x;
1373 break;
1374 case HALIGN_RIGHT:
1375 pos.UR.x += delx;
1376 pos.LL.x += delx;
1377 break;
1378 default:
1379 pos.LL.x += delx / 2;
1380 pos.UR.x -= delx / 2;
1381 break;
1382 }
1383 }
1384 dely = pos.UR.y - pos.LL.y - oldsz.y;
1385 if (dely > 0) {
1386 switch (cp->data.flags & VALIGN_MASK) {
1387 case VALIGN_BOTTOM:
1388 pos.UR.y = pos.LL.y + oldsz.y;
1389 break;
1390 case VALIGN_TOP:
1391 pos.UR.y += dely;
1392 pos.LL.y += dely;
1393 break;
1394 default:
1395 pos.LL.y += dely / 2;
1396 pos.UR.y -= dely / 2;
1397 break;
1398 }
1399 }
1400 }
1401 cp->data.box = pos;
1402 cp->data.sides = sides;
1403
1404 /* set up child's position */
1405 cbox.LL.x = pos.LL.x + cp->data.border + cp->data.pad;
1406 cbox.LL.y = pos.LL.y + cp->data.border + cp->data.pad;
1407 cbox.UR.x = pos.UR.x - cp->data.border - cp->data.pad;
1408 cbox.UR.y = pos.UR.y - cp->data.border - cp->data.pad;
1409
1410 if (cp->child.kind == HTML_TBL) {
1411 pos_html_tbl(cp->child.u.tbl, cbox, sides);
1412 } else if (cp->child.kind == HTML_IMAGE) {
1413 /* Note that alignment trumps scaling */
1414 oldsz = cp->child.u.img->box.UR;
1415 delx = cbox.UR.x - cbox.LL.x - oldsz.x;
1416 if (delx > 0) {
1417 switch (cp->data.flags & HALIGN_MASK) {
1418 case HALIGN_LEFT:
1419 cbox.UR.x -= delx;
1420 break;
1421 case HALIGN_RIGHT:
1422 cbox.LL.x += delx;
1423 break;
1424 default:
1425 break;
1426 }
1427 }
1428
1429 dely = cbox.UR.y - cbox.LL.y - oldsz.y;
1430 if (dely > 0) {
1431 switch (cp->data.flags & VALIGN_MASK) {
1432 case VALIGN_BOTTOM:
1433 cbox.UR.y -= dely;
1434 break;
1435 case VALIGN_TOP:
1436 cbox.LL.y += dely;
1437 break;
1438 default:
1439 break;
1440 }
1441 }
1442 pos_html_img(cp->child.u.img, cbox);
1443 } else {
1444 char dfltalign;
1445 int af;
1446
1447 oldsz = cp->child.u.txt->box.UR;
1448 delx = cbox.UR.x - cbox.LL.x - oldsz.x;
1449 /* If the cell is larger than the text block and alignment is
1450 * done at textblock level, the text box is shrunk accordingly.
1451 */
1452 if (delx > 0 && (af = (cp->data.flags & HALIGN_MASK)) != HALIGN_TEXT) {
1453 switch (af) {
1454 case HALIGN_LEFT:
1455 cbox.UR.x -= delx;
1456 break;
1457 case HALIGN_RIGHT:
1458 cbox.LL.x += delx;
1459 break;
1460 default:
1461 cbox.LL.x += delx / 2;
1462 cbox.UR.x -= delx / 2;
1463 break;
1464 }
1465 }
1466
1467 dely = cbox.UR.y - cbox.LL.y - oldsz.y;
1468 if (dely > 0) {
1469 switch (cp->data.flags & VALIGN_MASK) {
1470 case VALIGN_BOTTOM:
1471 cbox.UR.y -= dely;
1472 break;
1473 case VALIGN_TOP:
1474 cbox.LL.y += dely;
1475 break;
1476 default:
1477 cbox.LL.y += dely / 2;
1478 cbox.UR.y -= dely / 2;
1479 break;
1480 }
1481 }
1482 cp->child.u.txt->box = cbox;
1483
1484 /* Set default text alignment
1485 */
1486 switch (cp->data.flags & BALIGN_MASK) {
1487 case BALIGN_LEFT:
1488 dfltalign = 'l';
1489 break;
1490 case BALIGN_RIGHT:
1491 dfltalign = 'r';
1492 break;
1493 default:
1494 dfltalign = 'n';
1495 break;
1496 }
1497 pos_html_txt(cp->child.u.txt, dfltalign);
1498 }
1499}
1500
1501/* Position table given its box, then calculate
1502 * the position of each cell. In addition, set the sides
1503 * attribute indicating which external sides of the node
1504 * are accessible to the table.
1505 */
1506static void pos_html_tbl(htmltbl_t *tbl, boxf pos, unsigned char sides) {
1507 int plus;
1508 htmlcell_t **cells = tbl->cells;
1509 htmlcell_t *cp;
1510 boxf cbox;
1511
1512 if (tbl->parent && tbl->parent->data.pencolor && !tbl->data.pencolor)
1513 tbl->data.pencolor = gv_strdup(tbl->parent->data.pencolor);
1514
1515 double oldsz = tbl->data.box.UR.x;
1516 double delx = fmax(pos.UR.x - pos.LL.x - oldsz, 0);
1517 oldsz = tbl->data.box.UR.y;
1518 double dely = fmax(pos.UR.y - pos.LL.y - oldsz, 0);
1519
1520 /* If fixed, align box */
1521 if (tbl->data.flags & FIXED_FLAG) {
1522 if (delx > 0) {
1523 switch (tbl->data.flags & HALIGN_MASK) {
1524 case HALIGN_LEFT:
1525 pos.UR.x = pos.LL.x + oldsz;
1526 break;
1527 case HALIGN_RIGHT:
1528 pos.UR.x += delx;
1529 pos.LL.x += delx;
1530 break;
1531 default:
1532 pos.LL.x += delx / 2;
1533 pos.UR.x -= delx / 2;
1534 break;
1535 }
1536 delx = 0;
1537 }
1538 if (dely > 0) {
1539 switch (tbl->data.flags & VALIGN_MASK) {
1540 case VALIGN_BOTTOM:
1541 pos.UR.y = pos.LL.y + oldsz;
1542 break;
1543 case VALIGN_TOP:
1544 pos.LL.y += dely;
1545 pos.UR.y = pos.LL.y + oldsz;
1546 break;
1547 default:
1548 pos.LL.y += dely / 2;
1549 pos.UR.y -= dely / 2;
1550 break;
1551 }
1552 dely = 0;
1553 }
1554 }
1555
1556 /* change sizes to start positions and distribute extra space */
1557 double x = pos.LL.x + tbl->data.border + tbl->data.space;
1558 assert(tbl->column_count <= DBL_MAX);
1559 double extra = delx / (double)tbl->column_count;
1560 plus = ROUND(delx - extra * (double)tbl->column_count);
1561 for (size_t i = 0; i <= tbl->column_count; i++) {
1562 delx = tbl->widths[i] + extra + ((i <= INT_MAX && (int)i < plus) ? 1 : 0);
1563 tbl->widths[i] = x;
1564 x += delx + tbl->data.space;
1565 }
1566 double y = pos.UR.y - tbl->data.border - tbl->data.space;
1567 assert(tbl->row_count <= DBL_MAX);
1568 extra = dely / (double)tbl->row_count;
1569 plus = ROUND(dely - extra * (double)tbl->row_count);
1570 for (size_t i = 0; i <= tbl->row_count; i++) {
1571 dely = tbl->heights[i] + extra + ((i <= INT_MAX && (int)i < plus) ? 1 : 0);
1572 tbl->heights[i] = y;
1573 y -= dely + tbl->data.space;
1574 }
1575
1576 while ((cp = *cells++)) {
1577 unsigned char mask = 0;
1578 if (sides) {
1579 if (cp->col == 0)
1580 mask |= LEFT;
1581 if (cp->row == 0)
1582 mask |= TOP;
1583 if (cp->col + cp->colspan == tbl->column_count)
1584 mask |= RIGHT;
1585 if (cp->row + cp->rowspan == tbl->row_count)
1586 mask |= BOTTOM;
1587 }
1588 cbox.LL.x = tbl->widths[cp->col];
1589 cbox.UR.x = tbl->widths[cp->col + cp->colspan] - tbl->data.space;
1590 cbox.UR.y = tbl->heights[cp->row];
1591 cbox.LL.y = tbl->heights[cp->row + cp->rowspan] + tbl->data.space;
1592 pos_html_cell(cp, cbox, sides & mask);
1593 }
1594
1595 tbl->data.sides = sides;
1596 tbl->data.box = pos;
1597}
1598
1599/* Determine the size of a table by first determining the
1600 * size of each cell.
1601 */
1603 htmlenv_t *env) {
1604 int rv = 0;
1605 static textfont_t savef;
1606
1607 if (tbl->font)
1608 pushFontInfo(env, tbl->font, &savef);
1609 tbl->parent = parent;
1610 rv = processTbl(g, tbl, env);
1611
1612 /* Set up border and spacing */
1613 if (!(tbl->data.flags & SPACE_SET)) {
1615 }
1616 if (!(tbl->data.flags & BORDER_SET)) {
1617 tbl->data.border = DEFAULT_BORDER;
1618 }
1619
1620 set_cell_widths(tbl);
1621 set_cell_heights(tbl);
1622
1623 assert(tbl->column_count <= DBL_MAX);
1624 double wd =
1625 ((double)tbl->column_count + 1) * tbl->data.space + 2 * tbl->data.border;
1626 assert(tbl->row_count <= DBL_MAX);
1627 double ht =
1628 ((double)tbl->row_count + 1) * tbl->data.space + 2 * tbl->data.border;
1629 for (size_t i = 0; i < tbl->column_count; i++)
1630 wd += tbl->widths[i];
1631 for (size_t i = 0; i < tbl->row_count; i++)
1632 ht += tbl->heights[i];
1633
1634 if (tbl->data.flags & FIXED_FLAG) {
1635 if (tbl->data.width && tbl->data.height) {
1636 if (tbl->data.width < wd || tbl->data.height < ht) {
1637 agwarningf("table size too small for content\n");
1638 rv = 1;
1639 }
1640 wd = 0;
1641 ht = 0;
1642 } else {
1643 agwarningf("fixed table size with unspecified width or height\n");
1644 rv = 1;
1645 }
1646 }
1647 tbl->data.box.UR.x = fmax(wd, tbl->data.width);
1648 tbl->data.box.UR.y = fmax(ht, tbl->data.height);
1649
1650 if (tbl->font)
1651 popFontInfo(env, &savef);
1652 return rv;
1653}
1654
1655static char *nameOf(void *obj, agxbuf *xb) {
1656 Agedge_t *ep;
1657 switch (agobjkind(obj)) {
1658 case AGRAPH:
1659 agxbput(xb, agnameof(obj));
1660 break;
1661 case AGNODE:
1662 agxbput(xb, agnameof(obj));
1663 break;
1664 case AGEDGE:
1665 ep = obj;
1666 agxbput(xb, agnameof(agtail(ep)));
1667 agxbput(xb, agnameof(aghead(ep)));
1668 if (agisdirected(agraphof(aghead(ep))))
1669 agxbput(xb, "->");
1670 else
1671 agxbput(xb, "--");
1672 break;
1673 }
1674 return agxbuse(xb);
1675}
1676
1677#ifdef DEBUG
1678void indent(int i) {
1679 while (i--)
1680 fprintf(stderr, " ");
1681}
1682
1683void printBox(boxf b) {
1684 fprintf(stderr, "(%f,%f)(%f,%f)", b.LL.x, b.LL.y, b.UR.x, b.UR.y);
1685}
1686
1687void printImage(htmlimg_t *ip, int ind) {
1688 indent(ind);
1689 fprintf(stderr, "img: %s\n", ip->src);
1690}
1691
1692void printTxt(htmltxt_t *txt, int ind) {
1693 indent(ind);
1694 fprintf(stderr, "txt spans = %" PRISIZE_T " \n", txt->nspans);
1695 for (size_t i = 0; i < txt->nspans; i++) {
1696 indent(ind + 1);
1697 fprintf(stderr, "[%" PRISIZE_T "] %" PRISIZE_T " items\n", i,
1698 txt->spans[i].nitems);
1699 for (size_t j = 0; j < txt->spans[i].nitems; j++) {
1700 indent(ind + 2);
1701 fprintf(stderr, "[%" PRISIZE_T "] (%f,%f) \"%s\" ", j,
1702 txt->spans[i].items[j].size.x, txt->spans[i].items[j].size.y,
1703 txt->spans[i].items[j].str);
1704 if (txt->spans[i].items[j].font)
1705 fprintf(stderr, "font %s color %s size %f\n",
1706 txt->spans[i].items[j].font->name,
1707 txt->spans[i].items[j].font->color,
1708 txt->spans[i].items[j].font->size);
1709 else
1710 fprintf(stderr, "\n");
1711 }
1712 }
1713}
1714
1715void printData(htmldata_t *dp) {
1716 unsigned char flags = dp->flags;
1717 char c;
1718
1719 fprintf(stderr, "s%d(%d) ", dp->space, (flags & SPACE_SET ? 1 : 0));
1720 fprintf(stderr, "b%d(%d) ", dp->border, (flags & BORDER_SET ? 1 : 0));
1721 fprintf(stderr, "p%d(%d) ", dp->pad, (flags & PAD_SET ? 1 : 0));
1722 switch (flags & HALIGN_MASK) {
1723 case HALIGN_RIGHT:
1724 c = 'r';
1725 break;
1726 case HALIGN_LEFT:
1727 c = 'l';
1728 break;
1729 default:
1730 c = 'n';
1731 break;
1732 }
1733 fprintf(stderr, "%c", c);
1734 switch (flags & VALIGN_MASK) {
1735 case VALIGN_TOP:
1736 c = 't';
1737 break;
1738 case VALIGN_BOTTOM:
1739 c = 'b';
1740 break;
1741 default:
1742 c = 'c';
1743 break;
1744 }
1745 fprintf(stderr, "%c ", c);
1746 printBox(dp->box);
1747}
1748
1749void printTbl(htmltbl_t *tbl, int ind) {
1750 htmlcell_t **cells = tbl->cells;
1751 indent(ind);
1752 fprintf(stderr, "tbl (%p) %" PRISIZE_T " %" PRISIZE_T " ", tbl,
1753 tbl->column_count, tbl->row_count);
1754 printData(&tbl->data);
1755 fputs("\n", stderr);
1756 while (*cells)
1757 printCell(*cells++, ind + 1);
1758}
1759
1760static void printCell(htmlcell_t *cp, int ind) {
1761 indent(ind);
1762 fprintf(stderr, "cell %" PRIu16 " %" PRIu16 " %" PRIu16 " %" PRIu16 " ",
1763 cp->colspan, cp->colspan, cp->rowspan, cp->col, cp->row);
1764 printData(&cp->data);
1765 fputs("\n", stderr);
1766 switch (cp->child.kind) {
1767 case HTML_TBL:
1768 printTbl(cp->child.u.tbl, ind + 1);
1769 break;
1770 case HTML_TEXT:
1771 printTxt(cp->child.u.txt, ind + 1);
1772 break;
1773 case HTML_IMAGE:
1774 printImage(cp->child.u.img, ind + 1);
1775 break;
1776 default:
1777 break;
1778 }
1779}
1780
1781void printLbl(htmllabel_t *lbl) {
1782 if (lbl->kind == HTML_TBL)
1783 printTbl(lbl->u.tbl, 0);
1784 else
1785 printTxt(lbl->u.txt, 0);
1786}
1787#endif /* DEBUG */
1788
1789static char *getPenColor(void *obj) {
1790 char *str;
1791
1792 if ((str = agget(obj, "pencolor")) != 0 && str[0])
1793 return str;
1794 else if ((str = agget(obj, "color")) != 0 && str[0])
1795 return str;
1796 else
1797 return NULL;
1798}
1799
1801int make_html_label(void *obj, textlabel_t *lp) {
1802 int rv;
1803 double wd2, ht2;
1804 graph_t *g;
1805 htmllabel_t *lbl;
1806 htmlenv_t env;
1807 char *s;
1808
1809 env.obj = obj;
1810 switch (agobjkind(obj)) {
1811 case AGRAPH:
1812 env.g = ((Agraph_t *)obj)->root;
1813 break;
1814 case AGNODE:
1815 env.g = agraphof(obj);
1816 break;
1817 case AGEDGE:
1818 env.g = agraphof(aghead(((Agedge_t *)obj)));
1819 break;
1820 }
1821 g = env.g->root;
1822
1823 env.finfo.size = lp->fontsize;
1824 env.finfo.name = lp->fontname;
1825 env.finfo.color = lp->fontcolor;
1826 env.finfo.flags = 0;
1827 lbl = parseHTML(lp->text, &rv, &env);
1828 if (!lbl) {
1829 if (rv == 3) {
1830 // fatal error; `parseHTML` will have printed detail of it
1831 lp->html = false;
1832 return rv;
1833 }
1834 /* Parse of label failed; revert to simple text label */
1835 agxbuf xb = {0};
1836 lp->html = false;
1837 free(lp->text);
1838 lp->text = gv_strdup(nameOf(obj, &xb));
1839 switch (lp->charset) {
1840 case CHAR_LATIN1:
1841 s = latin1ToUTF8(lp->text);
1842 break;
1843 default: /* UTF8 */
1844 s = htmlEntityUTF8(lp->text, env.g);
1845 break;
1846 }
1847 free(lp->text);
1848 lp->text = s;
1849 make_simple_label(GD_gvc(g), lp);
1850 agxbfree(&xb);
1851 return rv;
1852 }
1853
1854 if (lbl->kind == HTML_TBL) {
1855 if (!lbl->u.tbl->data.pencolor && getPenColor(obj))
1856 lbl->u.tbl->data.pencolor = gv_strdup(getPenColor(obj));
1857 rv |= size_html_tbl(g, lbl->u.tbl, NULL, &env);
1858 wd2 = lbl->u.tbl->data.box.UR.x / 2;
1859 ht2 = lbl->u.tbl->data.box.UR.y / 2;
1860 boxf b = {{-wd2, -ht2}, {wd2, ht2}};
1861 pos_html_tbl(lbl->u.tbl, b, BOTTOM | RIGHT | TOP | LEFT);
1862 lp->dimen.x = b.UR.x - b.LL.x;
1863 lp->dimen.y = b.UR.y - b.LL.y;
1864 } else {
1865 rv |= size_html_txt(GD_gvc(g), lbl->u.txt, &env);
1866 wd2 = lbl->u.txt->box.UR.x / 2;
1867 ht2 = lbl->u.txt->box.UR.y / 2;
1868 boxf b = {{-wd2, -ht2}, {wd2, ht2}};
1869 lbl->u.txt->box = b;
1870 lp->dimen.x = b.UR.x - b.LL.x;
1871 lp->dimen.y = b.UR.y - b.LL.y;
1872 }
1873
1874 lp->u.html = lbl;
1875
1876 /* If the label is a table, replace label text because this may
1877 * be used for the title and alt fields in image maps.
1878 */
1879 if (lbl->kind == HTML_TBL) {
1880 free(lp->text);
1881 lp->text = gv_strdup("<TABLE>");
1882 }
1883
1884 return rv;
1885}
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
#define ROUND(f)
Definition arith.h:48
#define MAX(a, b)
Definition arith.h:33
API for compacted arrays of booleans.
static bitarray_t bitarray_new(size_t size_bits)
create an array of the given element length
Definition bitarray.h:47
static bool bitarray_get(bitarray_t self, size_t index)
get the value of the given element
Definition bitarray.h:65
static void bitarray_set(bitarray_t *self, size_t index, bool value)
set or clear the value of the given element
Definition bitarray.h:80
static void bitarray_reset(bitarray_t *self)
free underlying resources and leave a bit array empty
Definition bitarray.h:114
container data types API
#define dtinsert(d, o)
Definition cdt.h:186
#define parent(i)
Definition closest.c:75
char * latin1ToUTF8(char *s)
Converts string from Latin1 encoding to utf8. Also translates HTML entities.
Definition utils.c:1201
char * htmlEntityUTF8(char *s, graph_t *g)
Definition utils.c:1122
#define CHAR_LATIN1
Definition const.h:188
#define LEFT
Definition const.h:120
#define DEFAULT_COLOR
Definition const.h:48
#define RIGHT
Definition const.h:118
#define GRADIENT
Definition const.h:223
#define BOTTOM
Definition const.h:117
#define RGRADIENT
Definition const.h:224
#define TOP
Definition const.h:119
bool findStopColor(const char *colorlist, char *clrs[2], double *frac)
Definition emit.c:4264
obj_state_t * push_obj_state(GVJ_t *job)
Definition emit.c:110
void emit_map_rect(GVJ_t *job, boxf b)
Definition emit.c:630
void pop_obj_state(GVJ_t *job)
Definition emit.c:132
char * getObjId(GVJ_t *job, void *obj, agxbuf *xb)
Use id of root graph if any, plus kind and internal id of object.
Definition emit.c:204
bool initMapData(GVJ_t *job, char *lbl, char *url, char *tooltip, char *target, char *id, void *gobj)
Definition emit.c:162
#define A(n, t)
Definition expr.h:76
static int flags
Definition gc.c:63
#define B2BF(b, bf)
Definition geom.h:69
static WUR pointf scale(double c, pointf p)
Definition geomprocs.h:148
void free(void *)
#define FILL
Definition gmlparse.h:119
#define SIZE_MAX
Definition gmlscan.c:347
#define UINT16_MAX
Definition gmlscan.c:340
node NULL
Definition grammar.y:181
simple
Definition grammar.y:174
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:204
char * agget(void *obj, char *name)
Definition attr.c:447
#define agtail(e)
Definition cgraph.h:982
#define aghead(e)
Definition cgraph.h:983
void agwarningf(const char *fmt,...)
Definition agerror.c:175
void agerrorf(const char *fmt,...)
Definition agerror.c:167
int agisdirected(Agraph_t *g)
Definition graph.c:184
#define GD_has_images(g)
Definition types.h:369
#define GD_gvc(g)
Definition types.h:355
#define ND_label(n)
Definition types.h:502
Agraph_t * agraphof(void *obj)
Definition obj.c:187
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
int agobjkind(void *obj)
Definition obj.c:254
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
static uint64_t id
Definition gv2gml.c:42
static void indent(int ix)
Definition gv2gml.c:96
static GVC_t * gvc
Definition gv.cpp:27
Arithmetic helper functions.
#define EMIT_CLUSTERS_LAST
Definition gvcjob.h:84
@ LABEL_HTML
Definition gvcjob.h:38
@ CLUSTER_OBJTYPE
Definition gvcjob.h:168
@ EDGE_OBJTYPE
Definition gvcjob.h:168
@ ROOTGRAPH_OBJTYPE
Definition gvcjob.h:168
@ NODE_OBJTYPE
Definition gvcjob.h:168
static void color(Agraph_t *g)
Definition gvcolor.c:118
void gvrender_end_label(GVJ_t *job)
Definition gvrender.c:404
void gvrender_usershape(GVJ_t *job, char *name, pointf *AF, size_t n, bool filled, char *imagescale, char *imagepos)
Definition gvrender.c:670
void gvrender_set_style(GVJ_t *job, char **s)
Definition gvrender.c:481
void gvrender_set_fillcolor(GVJ_t *job, char *name)
Definition gvrender.c:450
void gvrender_polyline(GVJ_t *job, pointf *AF, size_t n)
Definition gvrender.c:596
point gvusershape_size(graph_t *g, char *name)
void gvrender_box(GVJ_t *job, boxf BF, int filled)
Definition gvrender.c:565
void gvrender_set_gradient_vals(GVJ_t *job, char *stopcolor, int angle, double frac)
Definition gvrender.c:467
void gvrender_begin_anchor(GVJ_t *job, char *href, char *tooltip, char *target, char *id)
Definition gvrender.c:373
void gvrender_end_anchor(GVJ_t *job)
Definition gvrender.c:384
void gvrender_textspan(GVJ_t *job, pointf p, textspan_t *span)
Definition gvrender.c:414
void gvrender_begin_label(GVJ_t *job, label_type type)
Definition gvrender.c:394
void gvrender_set_penwidth(GVJ_t *job, double penwidth)
Definition gvrender.c:798
void gvrender_set_pencolor(GVJ_t *job, char *name)
Definition gvrender.c:433
agxbput(xb, staging)
htmllabel_t * parseHTML(char *txt, int *warn, htmlenv_t *env)
Definition htmlparse.c:2052
textitem scanner parser str
Definition htmlparse.y:218
rows row
Definition htmlparse.y:320
cell HTML_TBL
Definition htmlparse.y:332
$2 rows
Definition htmlparse.y:292
static void free_html_img(htmlimg_t *ip)
Definition htmltable.c:798
static void endAnchor(GVJ_t *job, htmlmap_data_t *save)
Definition htmltable.c:422
static int size_html_tbl(graph_t *g, htmltbl_t *tbl, htmlcell_t *parent, htmlenv_t *env)
Definition htmltable.c:1602
static void pos_html_cell(htmlcell_t *cp, boxf pos, unsigned char sides)
Definition htmltable.c:1357
static void pushFontInfo(htmlenv_t *env, textfont_t *fp, textfont_t *savp)
Definition htmltable.c:78
static void pos_html_txt(htmltxt_t *ftxt, char c)
Set default alignment.
Definition htmltable.c:1350
static void set_cell_widths(htmltbl_t *table)
Definition htmltable.c:1212
#define RESET(fld)
Definition htmltable.c:406
static char * nameOf(void *obj, agxbuf *xb)
Definition htmltable.c:1655
static void allocObj(GVJ_t *job)
Definition htmltable.c:665
boxf * html_port(node_t *n, char *pname, unsigned char *sides)
Definition htmltable.c:886
static void pos_html_tbl(htmltbl_t *, boxf, unsigned char)
Definition htmltable.c:1506
static double heightOfLbl(htmllabel_t *lp)
Definition htmltable.c:705
int make_html_label(void *obj, textlabel_t *lp)
Return non-zero if problem parsing HTML. In this case, use object name.
Definition htmltable.c:1801
static void emit_html_img(GVJ_t *job, htmlimg_t *cp, htmlenv_t *env)
Definition htmltable.c:583
static void pos_html_img(htmlimg_t *cp, boxf pos)
Definition htmltable.c:1347
#define DEFAULT_CELLSPACING
Definition htmltable.c:58
#define DEFAULT_CELLPADDING
Definition htmltable.c:57
static int size_html_cell(graph_t *g, htmlcell_t *cp, htmltbl_t *parent, htmlenv_t *env)
Definition htmltable.c:1063
static void doBorder(GVJ_t *job, htmldata_t *dp, boxf b)
Definition htmltable.c:246
static void set_cell_heights(htmltbl_t *table)
Definition htmltable.c:1288
static pointf * mkPts(pointf *AF, boxf b, int border)
Definition htmltable.c:223
static void freeObj(GVJ_t *job)
Definition htmltable.c:695
static int processTbl(graph_t *g, htmltbl_t *tbl, htmlenv_t *env)
Definition htmltable.c:1150
static htmldata_t * portToTbl(htmltbl_t *, char *)
Definition htmltable.c:863
static void emit_html_txt(GVJ_t *job, htmltxt_t *tp, htmlenv_t *env)
Definition htmltable.c:193
static htmldata_t * portToCell(htmlcell_t *cp, char *id)
Definition htmltable.c:847
void free_html_text(htmltxt_t *t)
Definition htmltable.c:780
static void free_html_cell(htmlcell_t *cp)
Definition htmltable.c:803
static void emit_html_tbl(GVJ_t *job, htmltbl_t *tbl, htmlenv_t *env)
Definition htmltable.c:508
static void popFontInfo(htmlenv_t *env, textfont_t *savp)
Definition htmltable.c:105
static int size_html_txt(GVC_t *gvc, htmltxt_t *ftxt, htmlenv_t *env)
Definition htmltable.c:903
void free_html_label(htmllabel_t *lp, int root)
Definition htmltable.c:834
static void emit_html_cell(GVJ_t *job, htmlcell_t *cp, htmlenv_t *env)
Definition htmltable.c:609
static uint16_t findCol(PointSet *ps, int row, int col, htmlcell_t *cellp)
Definition htmltable.c:1119
void emit_html_label(GVJ_t *job, htmllabel_t *lp, textlabel_t *tp)
Definition htmltable.c:724
static void emit_htextspans(GVJ_t *job, size_t nspans, htextspan_t *spans, pointf p, double halfwidth_x, textfont_t finfo, boxf b, int simple)
Definition htmltable.c:114
static int initAnchor(GVJ_t *job, htmlenv_t *env, htmldata_t *data, boxf b, htmlmap_data_t *save)
Definition htmltable.c:372
void free_html_data(htmldata_t *dp)
Definition htmltable.c:770
static int setFill(GVJ_t *job, char *color, int angle, htmlstyle_t style, char *clrs[2])
Definition htmltable.c:340
static void doSide(GVJ_t *job, pointf p, double wd, double ht)
Definition htmltable.c:209
#define DEFAULT_BORDER
Definition htmltable.c:56
static void emit_html_rules(GVJ_t *job, htmlcell_t *cp, htmlenv_t *env, char *color, htmlcell_t *nextc)
Definition htmltable.c:439
static void free_html_tbl(htmltbl_t *tbl)
Definition htmltable.c:814
static int size_html_img(htmlimg_t *img, htmlenv_t *env)
Definition htmltable.c:1044
static char * getPenColor(void *obj)
Definition htmltable.c:1789
#define PAD_SET
Definition htmltable.h:35
#define BORDER_RIGHT
Definition htmltable.h:42
#define BORDER_TOP
Definition htmltable.h:41
#define HALIGN_TEXT
Definition htmltable.h:30
#define UNSET_ALIGN
Definition htmltable.h:46
#define HALIGN_LEFT
Definition htmltable.h:28
#define VALIGN_BOTTOM
Definition htmltable.h:32
#define BALIGN_MASK
Definition htmltable.h:39
#define BALIGN_RIGHT
Definition htmltable.h:37
#define HALIGN_MASK
Definition htmltable.h:29
#define BALIGN_LEFT
Definition htmltable.h:38
#define BORDER_BOTTOM
Definition htmltable.h:43
@ HTML_TEXT
Definition htmltable.h:103
@ HTML_IMAGE
Definition htmltable.h:103
#define SPACE_SET
Definition htmltable.h:36
#define VALIGN_MASK
Definition htmltable.h:33
#define BORDER_SET
Definition htmltable.h:34
#define BORDER_LEFT
Definition htmltable.h:40
#define BORDER_MASK
Definition htmltable.h:44
#define HALIGN_RIGHT
Definition htmltable.h:27
#define VALIGN_TOP
Definition htmltable.h:31
#define FIXED_FLAG
Definition htmltable.h:26
char * strdup_and_subst_obj(char *str, void *obj)
Processes graph object escape sequences; also collapses \ to .
Definition labels.c:387
void make_simple_label(GVC_t *gvc, textlabel_t *lp)
Definition labels.c:57
static int layout(graph_t *g, layout_info *infop, size_t *counter)
Definition layout.c:800
#define LIST_SIZE(list)
Definition list.h:92
#define LIST_FREE(list)
Definition list.h:413
#define LIST_GET(list, index)
Definition list.h:197
#define delta
Definition maze.c:138
static const int table[NTYPES][NTYPES]
Definition mincross.c:1707
void addPS(PointSet *ps, double x, double y)
Definition pointset.c:88
PointSet * newPS(void)
Definition pointset.c:70
void freePS(PointSet *ps)
Definition pointset.c:75
int isInPS(PointSet *ps, double x, double y)
Definition pointset.c:101
point containers PointSet and PointMap
static void printData(object_t *objs, size_t n_objs, xlabel_t *lbls, size_t n_lbls, label_params_t *params)
Definition postproc.c:217
#define PRISIZE_T
Definition prisize_t.h:25
void round_corners(GVJ_t *job, pointf *AF, size_t sides, graphviz_polygon_style_t style, int filled)
Handle some special graphical cases, such as rounding the shape, adding diagonals at corners,...
Definition shapes.c:710
pointf textspan_size(GVC_t *gvc, textspan_t *span)
Estimates size of a textspan, in points.
Definition textspan.c:73
platform abstraction for case-insensitive string functions
static bool streq(const char *a, const char *b)
are a and b equal?
Definition streq.h:11
graph or subgraph
Definition cgraph.h:424
Agraph_t * root
subgraphs - ancestors
Definition cgraph.h:433
Definition gvcint.h:81
char ** defaultlinestyle
Definition gvcint.h:149
Dt_t * textfont_dt
Definition gvcint.h:108
int flags
Definition gvcjob.h:299
obj_state_t * obj
Definition gvcjob.h:269
GVC_t * gvc
Definition gvcjob.h:263
Definition geom.h:39
point LL
Definition geom.h:39
point UR
Definition geom.h:39
Definition geom.h:41
pointf UR
Definition geom.h:41
pointf LL
Definition geom.h:41
result of partitioning available space, part of maze
Definition grid.h:33
Definition cdt.h:98
double lfsize
Definition htmltable.h:59
size_t nitems
Definition htmltable.h:56
textspan_t * items
Definition htmltable.h:55
double size
Definition htmltable.h:58
uint16_t rowspan
Definition htmltable.h:160
htmllabel_t child
Definition htmltable.h:163
uint16_t colspan
Definition htmltable.h:159
htmltbl_t * parent
Definition htmltable.h:164
bool vruled
vertically ruled?
Definition htmltable.h:165
bool hruled
horizontally ruled?
Definition htmltable.h:166
uint16_t col
Definition htmltable.h:161
uint16_t row
Definition htmltable.h:162
htmldata_t data
Definition htmltable.h:158
char * bgcolor
Definition htmltable.h:89
unsigned char border
Definition htmltable.h:93
char * target
Definition htmltable.h:86
char * id
Definition htmltable.h:88
signed char space
Definition htmltable.h:92
unsigned short width
Definition htmltable.h:97
unsigned short height
Definition htmltable.h:98
int gradientangle
Definition htmltable.h:91
char * port
Definition htmltable.h:85
unsigned short flags
Definition htmltable.h:96
unsigned char sides
Definition htmltable.h:95
char * href
Definition htmltable.h:84
unsigned char pad
Definition htmltable.h:94
char * pencolor
Definition htmltable.h:90
htmlstyle_t style
Definition htmltable.h:99
char * title
Definition htmltable.h:87
graph_t * g
Definition htmltable.h:173
textfont_t finfo
Definition htmltable.h:171
pointf pos
Definition htmltable.h:170
bool objid_set
Definition htmltable.h:176
char * objid
Definition htmltable.h:175
char * imgscale
Definition htmltable.h:174
void * obj
Definition htmltable.h:172
char * scale
Definition htmltable.h:72
char * src
Definition htmltable.h:71
boxf box
Definition htmltable.h:70
htmltxt_t * txt
Definition htmltable.h:151
htmltbl_t * tbl
Definition htmltable.h:150
htmlimg_t * img
Definition htmltable.h:152
label_type_t kind
Definition htmltable.h:154
union htmllabel_t::@50 u
bool explicit_tooltip
Definition htmltable.c:65
char * tooltip
Definition htmltable.c:62
char * target
Definition htmltable.c:63
bool dashed
Definition htmltable.h:80
bool dotted
Definition htmltable.h:79
bool rounded
Definition htmltable.h:77
bool radial
Definition htmltable.h:76
bool invisible
Definition htmltable.h:78
textfont_t * font
Definition htmltable.h:143
size_t row_count
number of rows
Definition htmltable.h:141
double * widths
widths of the columns
Definition htmltable.h:140
htmlcell_t ** cells
Definition htmltable.h:131
htmldata_t data
Definition htmltable.h:127
rows_t rows
cells
Definition htmltable.h:135
htmlcell_t * parent
Definition htmltable.h:130
double * heights
heights of the rows
Definition htmltable.h:139
size_t column_count
number of columns
Definition htmltable.h:142
boxf box
Definition htmltable.h:66
size_t nspans
Definition htmltable.h:64
char simple
Definition htmltable.h:65
htextspan_t * spans
Definition htmltable.h:63
graph_t * g
Definition gvcjob.h:186
edge_t * e
Definition gvcjob.h:189
char * tooltip
Definition gvcjob.h:216
char * url
Definition gvcjob.h:210
unsigned explicit_tooltip
Definition gvcjob.h:226
char * target
Definition gvcjob.h:221
obj_type type
Definition gvcjob.h:184
node_t * n
Definition gvcjob.h:188
union obj_state_s::@65 u
graph_t * sg
Definition gvcjob.h:187
char * id
Definition gvcjob.h:211
emit_state_t emit_state
Definition gvcjob.h:192
obj_state_t * parent
Definition gvcjob.h:182
Definition geom.h:27
int y
Definition geom.h:27
int x
Definition geom.h:27
double x
Definition geom.h:29
double y
Definition geom.h:29
bool ruled
Definition htmltable.h:115
char * color
Definition textspan.h:57
char * name
Definition textspan.h:56
PostscriptAlias * postscript_alias
Definition textspan.h:58
unsigned int flags
Definition textspan.h:61
double size
Definition textspan.h:59
pointf pos
Definition types.h:114
char * fontcolor
Definition types.h:107
char * text
Definition types.h:105
int charset
Definition types.h:108
char valign
Definition types.h:122
union textlabel_t::@54 u
double fontsize
Definition types.h:109
pointf space
Definition types.h:111
htmllabel_t * html
Definition types.h:120
char * fontname
Definition types.h:106
pointf dimen
Definition types.h:110
double yoffset_layout
Definition textspan.h:72
char * str
Definition textspan.h:68
char just
'l' 'n' 'r'
Definition textspan.h:74
void * layout
Definition textspan.h:70
pointf size
Definition textspan.h:73
textfont_t * font
Definition textspan.h:69
double yoffset_centerline
Definition textspan.h:72
void(* free_layout)(void *layout)
Definition textspan.h:71
Definition grammar.c:90
#define UNREACHABLE()
Definition unreachable.h:30