1 | /* $Id$Revision: */ |
2 | /* vim:set shiftwidth=4 ts=8: */ |
3 | |
4 | /************************************************************************* |
5 | * Copyright (c) 2011 AT&T Intellectual Property |
6 | * All rights reserved. This program and the accompanying materials |
7 | * are made available under the terms of the Eclipse Public License v1.0 |
8 | * which accompanies this distribution, and is available at |
9 | * http://www.eclipse.org/legal/epl-v10.html |
10 | * |
11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
12 | *************************************************************************/ |
13 | |
14 | |
15 | /* Implementation of HTML-like tables. |
16 | * |
17 | * The (now purged) CodeGen graphics model, especially with integral coodinates, is |
18 | * not adequate to handle this as we would like. In particular, it is |
19 | * difficult to handle notions of adjacency and correct rounding to pixels. |
20 | * For example, if 2 adjacent boxes bb1.UR.x == bb2.LL.x, the rectangles |
21 | * may be drawn overlapping. However, if we use bb1.UR.x+1 == bb2.LL.x |
22 | * there may or may not be a gap between them, even in the same device |
23 | * depending on their positions. When CELLSPACING > 1, this isn't as much |
24 | * of a problem. |
25 | * |
26 | * We allow negative spacing as a hack to allow overlapping cell boundaries. |
27 | * For the reasons discussed above, this is difficult to get correct. |
28 | * This is an important enough case we should extend the table model to |
29 | * support it correctly. This could be done by allowing a table attribute, |
30 | * e.g., CELLGRID=n, which sets CELLBORDER=0 and has the border drawing |
31 | * handled correctly by the table. |
32 | */ |
33 | |
34 | #include <assert.h> |
35 | #include "render.h" |
36 | #include "htmltable.h" |
37 | #include "agxbuf.h" |
38 | #include "pointset.h" |
39 | #include "intset.h" |
40 | #include "cdt.h" |
41 | |
42 | #define DEFAULT_BORDER 1 |
43 | #define DEFAULT_CELLPADDING 2 |
44 | #define DEFAULT_CELLSPACING 2 |
45 | |
46 | typedef struct { |
47 | char *url; |
48 | char *tooltip; |
49 | char *target; |
50 | char *id; |
51 | boolean explicit_tooltip; |
52 | point LL; |
53 | point UR; |
54 | } htmlmap_data_t; |
55 | |
56 | #ifdef DEBUG |
57 | static void printCell(htmlcell_t * cp, int ind); |
58 | #endif |
59 | |
60 | /* pushFontInfo: |
61 | * Replace current font attributes in env with ones from fp, |
62 | * storing old attributes in savp. We only deal with attributes |
63 | * set in env. The attributes are restored via popFontInfo. |
64 | */ |
65 | static void |
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 | |
125 | gvrender_begin_label(job, LABEL_HTML); |
126 | for (i = 0; i < nspans; i++) { |
127 | /* set p.x to leftmost point where the line of text begins */ |
128 | switch (spans[i].just) { |
129 | case 'l': |
130 | p.x = left_x; |
131 | break; |
132 | case 'r': |
133 | p.x = right_x - spans[i].size; |
134 | break; |
135 | default: |
136 | case 'n': |
137 | p.x = center_x - spans[i].size / 2.0; |
138 | break; |
139 | } |
140 | p_.y -= spans[i].lfsize; /* move to current base line */ |
141 | |
142 | ti = spans[i].items; |
143 | for (j = 0; j < spans[i].nitems; j++) { |
144 | if (ti->font && (ti->font->size > 0)) |
145 | tf.size = ti->font->size; |
146 | else |
147 | tf.size = finfo.size; |
148 | if (ti->font && ti->font->name) |
149 | tf.name = ti->font->name; |
150 | else |
151 | tf.name = finfo.name; |
152 | if (ti->font && ti->font->color) |
153 | tf.color = ti->font->color; |
154 | else |
155 | tf.color = finfo.color; |
156 | if (ti->font && ti->font->flags) |
157 | tf.flags = ti->font->flags; |
158 | else |
159 | tf.flags = 0; |
160 | |
161 | gvrender_set_pencolor(job, tf.color); |
162 | |
163 | tl.str = ti->str; |
164 | tl.font = &tf; |
165 | tl.yoffset_layout = ti->yoffset_layout; |
166 | if (simple) |
167 | tl.yoffset_centerline = ti->yoffset_centerline; |
168 | else |
169 | tl.yoffset_centerline = 1; |
170 | tl.font->postscript_alias = ti->font->postscript_alias; |
171 | tl.layout = ti->layout; |
172 | tl.size.x = ti->size.x; |
173 | tl.size.y = spans[i].lfsize; |
174 | tl.just = 'l'; |
175 | |
176 | p_.x = p.x; |
177 | gvrender_textspan(job, p_, &tl); |
178 | p.x += ti->size.x; |
179 | ti++; |
180 | } |
181 | } |
182 | |
183 | gvrender_end_label(job); |
184 | } |
185 | |
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 |
260 | gvrender_set_style(job, job->gvc->defaultlinestyle); |
261 | gvrender_set_penwidth(job, dp->border); |
262 | |
263 | if (dp->style & ROUNDED) |
264 | round_corners(job, mkPts(AF, b, dp->border), 4, ROUNDED, 0); |
265 | else if ((sides = (dp->flags & BORDER_MASK))) { |
266 | mkPts (AF+1, b, dp->border); /* AF[1-4] has LL=SW,SE,UR=NE,NW */ |
267 | switch (sides) { |
268 | case BORDER_BOTTOM : |
269 | gvrender_polyline(job, AF+1, 2); |
270 | break; |
271 | case BORDER_RIGHT : |
272 | gvrender_polyline(job, AF+2, 2); |
273 | break; |
274 | case BORDER_TOP : |
275 | gvrender_polyline(job, AF+3, 2); |
276 | break; |
277 | case BORDER_LEFT : |
278 | AF[0] = AF[4]; |
279 | gvrender_polyline(job, AF, 2); |
280 | break; |
281 | case BORDER_BOTTOM|BORDER_RIGHT : |
282 | gvrender_polyline(job, AF+1, 3); |
283 | break; |
284 | case BORDER_RIGHT|BORDER_TOP : |
285 | gvrender_polyline(job, AF+2, 3); |
286 | break; |
287 | case BORDER_TOP|BORDER_LEFT : |
288 | AF[5] = AF[1]; |
289 | gvrender_polyline(job, AF+3, 3); |
290 | break; |
291 | case BORDER_LEFT|BORDER_BOTTOM : |
292 | AF[0] = AF[4]; |
293 | gvrender_polyline(job, AF, 3); |
294 | break; |
295 | case BORDER_BOTTOM|BORDER_RIGHT|BORDER_TOP : |
296 | gvrender_polyline(job, AF+1, 4); |
297 | break; |
298 | case BORDER_RIGHT|BORDER_TOP|BORDER_LEFT : |
299 | AF[5] = AF[1]; |
300 | gvrender_polyline(job, AF+2, 4); |
301 | break; |
302 | case BORDER_TOP|BORDER_LEFT|BORDER_BOTTOM : |
303 | AF[5] = AF[1]; |
304 | AF[6] = AF[2]; |
305 | gvrender_polyline(job, AF+3, 4); |
306 | break; |
307 | case BORDER_LEFT|BORDER_BOTTOM|BORDER_RIGHT : |
308 | AF[0] = AF[4]; |
309 | gvrender_polyline(job, AF, 4); |
310 | break; |
311 | case BORDER_TOP|BORDER_BOTTOM : |
312 | gvrender_polyline(job, AF+1, 2); |
313 | gvrender_polyline(job, AF+3, 2); |
314 | break; |
315 | case BORDER_LEFT|BORDER_RIGHT : |
316 | AF[0] = AF[4]; |
317 | gvrender_polyline(job, AF, 2); |
318 | gvrender_polyline(job, AF+2, 2); |
319 | break; |
320 | } |
321 | } else { |
322 | if (dp->border > 1) { |
323 | double delta = ((double) dp->border) / 2.0; |
324 | b.LL.x += delta; |
325 | b.LL.y += delta; |
326 | b.UR.x -= delta; |
327 | b.UR.y -= delta; |
328 | } |
329 | gvrender_box(job, b, 0); |
330 | } |
331 | } |
332 | |
333 | /* setFill: |
334 | * Set up fill values from given color; make pen transparent. |
335 | * Return type of fill required. |
336 | */ |
337 | static int |
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); |
411 | gvrender_begin_anchor(job, |
412 | obj->url, obj->tooltip, obj->target, |
413 | obj->id); |
414 | } |
415 | } |
416 | return changed; |
417 | } |
418 | |
419 | #define RESET(fld) \ |
420 | if(obj->fld != save->fld) {free(obj->fld); obj->fld = save->fld;} |
421 | |
422 | /* endAnchor: |
423 | * Pop context pushed by initAnchor. |
424 | * This is done by ending current anchor, restoring old values and |
425 | * freeing new. |
426 | * |
427 | * NB: We don't save or restore geometric map info. This is because |
428 | * this preservation of map context is only necessary for SVG-like |
429 | * systems where graphical items are wrapped in an anchor, and we map |
430 | * top-down. For ordinary map anchors, this is all done bottom-up, so |
431 | * the geometric map info at the higher level hasn't been emitted yet. |
432 | */ |
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; |
689 | obj_state_t *parent; |
690 | |
691 | obj = push_obj_state(job); |
692 | parent = obj->parent; |
693 | obj->type = parent->type; |
694 | obj->emit_state = parent->emit_state; |
695 | switch (obj->type) { |
696 | case NODE_OBJTYPE: |
697 | obj->u.n = parent->u.n; |
698 | break; |
699 | case ROOTGRAPH_OBJTYPE: |
700 | obj->u.g = parent->u.g; |
701 | break; |
702 | case CLUSTER_OBJTYPE: |
703 | obj->u.sg = parent->u.sg; |
704 | break; |
705 | case EDGE_OBJTYPE: |
706 | obj->u.e = parent->u.e; |
707 | break; |
708 | } |
709 | obj->url = parent->url; |
710 | obj->tooltip = parent->tooltip; |
711 | obj->target = parent->target; |
712 | obj->explicit_tooltip = parent->explicit_tooltip; |
713 | } |
714 | |
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 | */ |
747 | void emit_html_label(GVJ_t * job, htmllabel_t * lp, textlabel_t * tp) |
748 | { |
749 | htmlenv_t env; |
750 | pointf p; |
751 | |
752 | allocObj(job); |
753 | |
754 | p = tp->pos; |
755 | switch (tp->valign) { |
756 | case 't': |
757 | p.y = tp->pos.y + (tp->space.y - heightOfLbl(lp))/ 2.0 - 1; |
758 | break; |
759 | case 'b': |
760 | p.y = tp->pos.y - (tp->space.y - heightOfLbl(lp))/ 2.0 - 1; |
761 | break; |
762 | default: |
763 | /* no-op */ |
764 | break; |
765 | } |
766 | env.pos = p; |
767 | env.finfo.color = tp->fontcolor; |
768 | env.finfo.name = tp->fontname; |
769 | env.finfo.size = tp->fontsize; |
770 | env.imgscale = agget(job->obj->u.n, "imagescale" ); |
771 | env.objid = job->obj->id; |
772 | env.objid_set = 0; |
773 | if ((env.imgscale == NULL) || (env.imgscale[0] == '\0')) |
774 | env.imgscale = "false" ; |
775 | if (lp->kind == HTML_TBL) { |
776 | htmltbl_t *tbl = lp->u.tbl; |
777 | |
778 | /* set basic graphics context */ |
779 | /* Need to override line style set by node. */ |
780 | gvrender_set_style(job, job->gvc->defaultlinestyle); |
781 | if (tbl->data.pencolor) |
782 | gvrender_set_pencolor(job, tbl->data.pencolor); |
783 | else |
784 | gvrender_set_pencolor(job, DEFAULT_COLOR); |
785 | emit_html_tbl(job, tbl, &env); |
786 | } else { |
787 | emit_html_txt(job, lp->u.txt, &env); |
788 | } |
789 | if (env.objid_set) |
790 | free(env.objid); |
791 | freeObj(job); |
792 | } |
793 | |
794 | void free_html_data(htmldata_t * dp) |
795 | { |
796 | free(dp->href); |
797 | free(dp->port); |
798 | free(dp->target); |
799 | free(dp->id); |
800 | free(dp->title); |
801 | free(dp->bgcolor); |
802 | free(dp->pencolor); |
803 | } |
804 | |
805 | void free_html_text(htmltxt_t * t) |
806 | { |
807 | htextspan_t *tl; |
808 | textspan_t *ti; |
809 | int i, j; |
810 | |
811 | if (!t) |
812 | return; |
813 | |
814 | tl = t->spans; |
815 | for (i = 0; i < t->nspans; i++) { |
816 | ti = tl->items; |
817 | for (j = 0; j < tl->nitems; j++) { |
818 | if (ti->str) |
819 | free(ti->str); |
820 | if (ti->layout && ti->free_layout) |
821 | ti->free_layout(ti->layout); |
822 | ti++; |
823 | } |
824 | tl++; |
825 | } |
826 | if (t->spans) |
827 | free(t->spans); |
828 | free(t); |
829 | } |
830 | |
831 | void free_html_img(htmlimg_t * ip) |
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; |
1097 | ftxt->spans[i].items[j].yoffset_centerline = lp.yoffset_centerline; |
1098 | ftxt->spans[i].items[j].font = lp.font; |
1099 | ftxt->spans[i].items[j].layout = lp.layout; |
1100 | ftxt->spans[i].items[j].free_layout = lp.free_layout; |
1101 | width += sz.x; |
1102 | mxfsize = MAX(tf.size, mxfsize); |
1103 | mxysize = MAX(sz.y, mxysize); |
1104 | maxoffset = MAX(lp.yoffset_centerline, maxoffset); |
1105 | } |
1106 | /* lsize = mxfsize * LINESPACING; */ |
1107 | ftxt->spans[i].size = width; |
1108 | /* ysize - curbline is the distance from the previous |
1109 | * baseline to the bottom of the previous line. |
1110 | * Then, in the current line, we set the baseline to |
1111 | * be 5/6 of the max. font size. Thus, lfsize gives the |
1112 | * distance from the previous baseline to the new one. |
1113 | */ |
1114 | /* ftxt->spans[i].lfsize = 5*mxfsize/6 + ysize - curbline; */ |
1115 | if (simple) { |
1116 | lsize = mxysize; |
1117 | if (i == 0) |
1118 | ftxt->spans[i].lfsize = mxfsize; |
1119 | else |
1120 | ftxt->spans[i].lfsize = mxysize; |
1121 | } |
1122 | else { |
1123 | lsize = mxfsize; |
1124 | if (i == 0) |
1125 | ftxt->spans[i].lfsize = mxfsize - maxoffset; |
1126 | else |
1127 | ftxt->spans[i].lfsize = mxfsize + ysize - curbline - maxoffset; |
1128 | } |
1129 | curbline += ftxt->spans[i].lfsize; |
1130 | xsize = MAX(width, xsize); |
1131 | ysize += lsize; |
1132 | } |
1133 | ftxt->box.UR.x = xsize; |
1134 | if (ftxt->nspans == 1) |
1135 | ftxt->box.UR.y = mxysize; |
1136 | else |
1137 | ftxt->box.UR.y = ysize; |
1138 | return 0; |
1139 | } |
1140 | |
1141 | /* forward declarion for recursive usage */ |
1142 | static int size_html_tbl(graph_t * g, htmltbl_t * tbl, htmlcell_t * parent, |
1143 | htmlenv_t * env); |
1144 | |
1145 | /* size_html_img: |
1146 | */ |
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 |
1182 | cp->data.pad = DEFAULT_CELLPADDING; |
1183 | } |
1184 | if (!(cp->data.flags & BORDER_SET)) { |
1185 | if (parent->cb >= 0) |
1186 | cp->data.border = parent->cb; |
1187 | else if (parent->data.flags & BORDER_SET) |
1188 | cp->data.border = parent->data.border; |
1189 | else |
1190 | cp->data.border = DEFAULT_BORDER; |
1191 | } |
1192 | |
1193 | if (cp->child.kind == HTML_TBL) { |
1194 | rv = size_html_tbl(g, cp->child.u.tbl, cp, env); |
1195 | child_sz = cp->child.u.tbl->data.box.UR; |
1196 | } else if (cp->child.kind == HTML_IMAGE) { |
1197 | rv = size_html_img(cp->child.u.img, env); |
1198 | child_sz = cp->child.u.img->box.UR; |
1199 | } else { |
1200 | rv = size_html_txt(GD_gvc(g), cp->child.u.txt, env); |
1201 | child_sz = cp->child.u.txt->box.UR; |
1202 | } |
1203 | |
1204 | margin = 2 * (cp->data.pad + cp->data.border); |
1205 | sz.x = child_sz.x + margin; |
1206 | sz.y = child_sz.y + margin; |
1207 | |
1208 | if (cp->data.flags & FIXED_FLAG) { |
1209 | if (cp->data.width && cp->data.height) { |
1210 | if (((cp->data.width < sz.x) || (cp->data.height < sz.y)) && (cp->child.kind != HTML_IMAGE)) { |
1211 | agerr(AGWARN, "cell size too small for content\n" ); |
1212 | rv = 1; |
1213 | } |
1214 | sz.x = sz.y = 0; |
1215 | |
1216 | } else { |
1217 | agerr(AGWARN, |
1218 | "fixed cell size with unspecified width or height\n" ); |
1219 | rv = 1; |
1220 | } |
1221 | } |
1222 | cp->data.box.UR.x = MAX(sz.x, cp->data.width); |
1223 | cp->data.box.UR.y = MAX(sz.y, cp->data.height); |
1224 | return rv; |
1225 | } |
1226 | |
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 | */ |
1336 | void sizeLinearArray(htmltbl_t * tbl) |
1337 | { |
1338 | htmlcell_t *cp; |
1339 | htmlcell_t **cells; |
1340 | int wd, ht, i, x, y; |
1341 | |
1342 | tbl->heights = N_NEW(tbl->rc + 1, int); |
1343 | tbl->widths = N_NEW(tbl->cc + 1, int); |
1344 | |
1345 | for (cells = tbl->u.n.cells; *cells; cells++) { |
1346 | cp = *cells; |
1347 | if (cp->rspan == 1) |
1348 | ht = cp->data.box.UR.y; |
1349 | else { |
1350 | ht = SPLIT(cp->data.box.UR.y, cp->rspan, tbl->data.space); |
1351 | ht = MAX(ht, 1); |
1352 | } |
1353 | if (cp->cspan == 1) |
1354 | wd = cp->data.box.UR.x; |
1355 | else { |
1356 | wd = SPLIT(cp->data.box.UR.x, cp->cspan, tbl->data.space); |
1357 | wd = MAX(wd, 1); |
1358 | } |
1359 | for (i = cp->row; i < cp->row + cp->rspan; i++) { |
1360 | y = tbl->heights[i]; |
1361 | tbl->heights[i] = MAX(y, ht); |
1362 | } |
1363 | for (i = cp->col; i < cp->col + cp->cspan; i++) { |
1364 | x = tbl->widths[i]; |
1365 | tbl->widths[i] = MAX(x, wd); |
1366 | } |
1367 | } |
1368 | } |
1369 | |
1370 | static char *nnames[] = { |
1371 | "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , |
1372 | "10" , "11" , "12" , "13" , "14" , "15" , "16" , "17" , "18" , "19" , "20" , |
1373 | }; |
1374 | |
1375 | /* nToName: |
1376 | * Convert int to its decimal string representation. |
1377 | */ |
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 | */ |
1541 | void sizeArray(htmltbl_t * tbl) |
1542 | { |
1543 | graph_t *rowg; |
1544 | graph_t *colg; |
1545 | #ifdef _WIN32 |
1546 | Agdesc_t dir = { 1, 1, 0, 1 }; |
1547 | #else |
1548 | Agdesc_t dir = Agstrictdirected; |
1549 | #endif |
1550 | |
1551 | /* Do the 1D cases by hand */ |
1552 | if ((tbl->rc == 1) || (tbl->cc == 1)) { |
1553 | sizeLinearArray(tbl); |
1554 | return; |
1555 | } |
1556 | |
1557 | tbl->heights = N_NEW(tbl->rc + 1, int); |
1558 | tbl->widths = N_NEW(tbl->cc + 1, int); |
1559 | |
1560 | rowg = agopen("rowg" , dir, NIL(Agdisc_t *)); |
1561 | colg = agopen("colg" , dir, NIL(Agdisc_t *)); |
1562 | /* Only need GD_nlist */ |
1563 | agbindrec(rowg, "Agraphinfo_t" , sizeof(Agraphinfo_t), TRUE); // graph custom data |
1564 | agbindrec(colg, "Agraphinfo_t" , sizeof(Agraphinfo_t), TRUE); // graph custom data |
1565 | makeGraphs(tbl, rowg, colg); |
1566 | rank(rowg, 2, INT_MAX); |
1567 | rank(colg, 2, INT_MAX); |
1568 | setSizes(tbl, rowg, colg); |
1569 | closeGraphs(rowg, colg); |
1570 | } |
1571 | |
1572 | static void pos_html_tbl(htmltbl_t *, boxf, int); /* forward declaration */ |
1573 | |
1574 | /* pos_html_img: |
1575 | * Place image in cell |
1576 | * storing allowed space handed by parent cell. |
1577 | * How this space is used is handled in emit_html_img. |
1578 | */ |
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, , plus; |
1751 | htmlcell_t **cells = tbl->u.n.cells; |
1752 | htmlcell_t *cp; |
1753 | boxf cbox; |
1754 | |
1755 | if (tbl->u.n.parent && tbl->u.n.parent->data.pencolor |
1756 | && !tbl->data.pencolor) |
1757 | tbl->data.pencolor = strdup(tbl->u.n.parent->data.pencolor); |
1758 | |
1759 | oldsz = tbl->data.box.UR.x; |
1760 | delx = (pos.UR.x - pos.LL.x) - oldsz; |
1761 | assert(delx >= 0); |
1762 | oldsz = tbl->data.box.UR.y; |
1763 | dely = (pos.UR.y - pos.LL.y) - oldsz; |
1764 | assert(dely >= 0); |
1765 | |
1766 | /* If fixed, align box */ |
1767 | if (tbl->data.flags & FIXED_FLAG) { |
1768 | if (delx > 0) { |
1769 | switch (tbl->data.flags & HALIGN_MASK) { |
1770 | case HALIGN_LEFT: |
1771 | pos.UR.x = pos.LL.x + oldsz; |
1772 | break; |
1773 | case HALIGN_RIGHT: |
1774 | pos.UR.x += delx; |
1775 | pos.LL.x += delx; |
1776 | break; |
1777 | default: |
1778 | pos.LL.x += delx / 2; |
1779 | pos.UR.x -= delx / 2; |
1780 | break; |
1781 | } |
1782 | delx = 0; |
1783 | } |
1784 | if (dely > 0) { |
1785 | switch (tbl->data.flags & VALIGN_MASK) { |
1786 | case VALIGN_BOTTOM: |
1787 | pos.UR.y = pos.LL.y + oldsz; |
1788 | break; |
1789 | case VALIGN_TOP: |
1790 | pos.LL.y += dely; |
1791 | pos.UR.y = pos.LL.y + oldsz; |
1792 | break; |
1793 | default: |
1794 | pos.LL.y += dely / 2; |
1795 | pos.UR.y -= dely / 2; |
1796 | break; |
1797 | } |
1798 | dely = 0; |
1799 | } |
1800 | } |
1801 | |
1802 | /* change sizes to start positions and distribute extra space */ |
1803 | x = pos.LL.x + tbl->data.border + tbl->data.space; |
1804 | extra = delx / (tbl->cc); |
1805 | plus = ROUND(delx - extra * (tbl->cc)); |
1806 | for (i = 0; i <= tbl->cc; i++) { |
1807 | delx = tbl->widths[i] + extra + (i < plus ? 1 : 0); |
1808 | tbl->widths[i] = x; |
1809 | x += delx + tbl->data.space; |
1810 | } |
1811 | y = pos.UR.y - tbl->data.border - tbl->data.space; |
1812 | extra = dely / (tbl->rc); |
1813 | plus = ROUND(dely - extra * (tbl->rc)); |
1814 | for (i = 0; i <= tbl->rc; i++) { |
1815 | dely = tbl->heights[i] + extra + (i < plus ? 1 : 0); |
1816 | tbl->heights[i] = y; |
1817 | y -= dely + tbl->data.space; |
1818 | } |
1819 | |
1820 | while ((cp = *cells++)) { |
1821 | int mask = 0; |
1822 | if (sides) { |
1823 | if (cp->col == 0) |
1824 | mask |= LEFT; |
1825 | if (cp->row == 0) |
1826 | mask |= TOP; |
1827 | if (cp->col + cp->cspan == tbl->cc) |
1828 | mask |= RIGHT; |
1829 | if (cp->row + cp->rspan == tbl->rc) |
1830 | mask |= BOTTOM; |
1831 | } |
1832 | cbox.LL.x = tbl->widths[cp->col]; |
1833 | cbox.UR.x = tbl->widths[cp->col + cp->cspan] - tbl->data.space; |
1834 | cbox.UR.y = tbl->heights[cp->row]; |
1835 | cbox.LL.y = tbl->heights[cp->row + cp->rspan] + tbl->data.space; |
1836 | pos_html_cell(cp, cbox, sides & mask); |
1837 | } |
1838 | |
1839 | tbl->data.sides = sides; |
1840 | tbl->data.box = pos; |
1841 | } |
1842 | |
1843 | /* size_html_tbl: |
1844 | * Determine the size of a table by first determining the |
1845 | * size of each cell. |
1846 | */ |
1847 | static int |
1848 | size_html_tbl(graph_t * g, htmltbl_t * tbl, htmlcell_t * parent, |
1849 | htmlenv_t * env) |
1850 | { |
1851 | int i, wd, ht; |
1852 | int rv = 0; |
1853 | static textfont_t savef; |
1854 | |
1855 | if (tbl->font) |
1856 | pushFontInfo(env, tbl->font, &savef); |
1857 | tbl->u.n.parent = parent; |
1858 | rv = processTbl(g, tbl, env); |
1859 | |
1860 | /* Set up border and spacing */ |
1861 | if (!(tbl->data.flags & SPACE_SET)) { |
1862 | tbl->data.space = DEFAULT_CELLSPACING; |
1863 | } |
1864 | if (!(tbl->data.flags & BORDER_SET)) { |
1865 | tbl->data.border = DEFAULT_BORDER; |
1866 | } |
1867 | |
1868 | sizeArray(tbl); |
1869 | |
1870 | wd = (tbl->cc + 1) * tbl->data.space + 2 * tbl->data.border; |
1871 | ht = (tbl->rc + 1) * tbl->data.space + 2 * tbl->data.border; |
1872 | for (i = 0; i < tbl->cc; i++) |
1873 | wd += tbl->widths[i]; |
1874 | for (i = 0; i < tbl->rc; i++) |
1875 | ht += tbl->heights[i]; |
1876 | |
1877 | if (tbl->data.flags & FIXED_FLAG) { |
1878 | if (tbl->data.width && tbl->data.height) { |
1879 | if ((tbl->data.width < wd) || (tbl->data.height < ht)) { |
1880 | agerr(AGWARN, "table size too small for content\n" ); |
1881 | rv = 1; |
1882 | } |
1883 | wd = ht = 0; |
1884 | } else { |
1885 | agerr(AGWARN, |
1886 | "fixed table size with unspecified width or height\n" ); |
1887 | rv = 1; |
1888 | } |
1889 | } |
1890 | tbl->data.box.UR.x = MAX(wd, tbl->data.width); |
1891 | tbl->data.box.UR.y = MAX(ht, tbl->data.height); |
1892 | |
1893 | if (tbl->font) |
1894 | popFontInfo(env, &savef); |
1895 | return rv; |
1896 | } |
1897 | |
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 | } |
2140 | |