123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- /// @file
- /// @ingroup common_render
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include <common/render.h>
- #include <common/htmltable.h>
- #include <limits.h>
- #include <stdbool.h>
- #include <stddef.h>
- #include <util/agxbuf.h>
- #include <util/alloc.h>
- static char *strdup_and_subst_obj0 (char *str, void *obj, int escBackslash);
- static void storeline(GVC_t *gvc, textlabel_t *lp, char *line,
- char terminator) {
- pointf size;
- textspan_t *span;
- size_t oldsz = lp->u.txt.nspans + 1;
- lp->u.txt.span = gv_recalloc(lp->u.txt.span, oldsz, oldsz + 1,
- sizeof(textspan_t));
- span = &lp->u.txt.span[lp->u.txt.nspans];
- span->str = line;
- span->just = terminator;
- if (line && line[0]) {
- textfont_t tf = {0};
- tf.name = lp->fontname;
- tf.size = lp->fontsize;
- span->font = dtinsert(gvc->textfont_dt, &tf);
- size = textspan_size(gvc, span);
- }
- else {
- size.x = 0.0;
- span->size.y = size.y = (int)(lp->fontsize * LINESPACING);
- }
- lp->u.txt.nspans++;
- /* width = max line width */
- lp->dimen.x = MAX(lp->dimen.x, size.x);
- /* accumulate height */
- lp->dimen.y += size.y;
- }
- /* compiles <str> into a label <lp> */
- void make_simple_label(GVC_t * gvc, textlabel_t * lp)
- {
- lp->dimen.x = lp->dimen.y = 0.0;
- if (*lp->text == '\0')
- return;
- agxbuf line = {0};
- for (char c, *p = lp->text; (c = *p++);) {
- unsigned char byte = (unsigned char) c;
- /* wingraphviz allows a combination of ascii and big-5. The latter
- * is a two-byte encoding, with the first byte in 0xA1-0xFE, and
- * the second in 0x40-0x7e or 0xa1-0xfe. We assume that the input
- * is well-formed, but check that we don't go past the ending '\0'.
- */
- if (lp->charset == CHAR_BIG5 && 0xA1 <= byte && byte <= 0xFE) {
- agxbputc(&line, c);
- c = *p++;
- agxbputc(&line, c);
- if (!c) /* NB. Protect against unexpected string end here */
- break;
- } else {
- if (c == '\\') {
- switch (*p) {
- case 'n':
- case 'l':
- case 'r':
- storeline(gvc, lp, agxbdisown(&line), *p);
- break;
- default:
- agxbputc(&line, *p);
- }
- if (*p)
- p++;
- /* tcldot can enter real linend characters */
- } else if (c == '\n') {
- storeline(gvc, lp, agxbdisown(&line), 'n');
- } else {
- agxbputc(&line, c);
- }
- }
- }
- if (agxblen(&line) > 0) {
- storeline(gvc, lp, agxbdisown(&line), 'n');
- }
- agxbfree(&line);
- lp->space = lp->dimen;
- }
- /* make_label:
- * Assume str is freshly allocated for this instance, so it
- * can be freed in free_label.
- */
- textlabel_t *make_label(void *obj, char *str, int kind, double fontsize, char *fontname, char *fontcolor)
- {
- textlabel_t *rv = gv_alloc(sizeof(textlabel_t));
- graph_t *g = NULL, *sg = NULL;
- node_t *n = NULL;
- edge_t *e = NULL;
- char *s;
- switch (agobjkind(obj)) {
- case AGRAPH:
- sg = obj;
- g = sg->root;
- break;
- case AGNODE:
- n = obj;
- g = agroot(agraphof(n));
- break;
- case AGEDGE:
- e = obj;
- g = agroot(agraphof(aghead(e)));
- break;
- }
- rv->fontname = fontname;
- rv->fontcolor = fontcolor;
- rv->fontsize = fontsize;
- rv->charset = GD_charset(g);
- if (kind & LT_RECD) {
- rv->text = gv_strdup(str);
- if (kind & LT_HTML) {
- rv->html = true;
- }
- }
- else if (kind == LT_HTML) {
- rv->text = gv_strdup(str);
- rv->html = true;
- if (make_html_label(obj, rv)) {
- switch (agobjkind(obj)) {
- case AGRAPH:
- agerr(AGPREV, "in label of graph %s\n",agnameof(sg));
- break;
- case AGNODE:
- agerr(AGPREV, "in label of node %s\n", agnameof(n));
- break;
- case AGEDGE:
- agerr(AGPREV, "in label of edge %s %s %s\n",
- agnameof(agtail(e)), agisdirected(g)?"->":"--", agnameof(aghead(e)));
- break;
- }
- }
- }
- else {
- assert(kind == LT_NONE);
- /* This call just processes the graph object based escape sequences. The formatting escape
- * sequences (\n, \l, \r) are processed in make_simple_label. That call also replaces \\ with \.
- */
- rv->text = strdup_and_subst_obj0(str, obj, 0);
- switch (rv->charset) {
- case CHAR_LATIN1:
- s = latin1ToUTF8(rv->text);
- break;
- default: /* UTF8 */
- s = htmlEntityUTF8(rv->text, g);
- break;
- }
- free(rv->text);
- rv->text = s;
- make_simple_label(GD_gvc(g), rv);
- }
- return rv;
- }
- /* free_textspan:
- * Free resources related to textspan_t.
- * tl is an array of cnt textspan_t's.
- * It is also assumed that the text stored in the str field
- * is all stored in one large buffer shared by all of the textspan_t,
- * so only the first one needs to free its tlp->str.
- */
- void free_textspan(textspan_t *tl, size_t cnt) {
- textspan_t* tlp = tl;
- if (!tl) return;
- for (size_t i = 0; i < cnt; i++) {
- free(tlp->str);
- if (tlp->layout && tlp->free_layout)
- tlp->free_layout (tlp->layout);
- tlp++;
- }
- free(tl);
- }
- void free_label(textlabel_t * p)
- {
- if (p) {
- free(p->text);
- if (p->html) {
- if (p->u.html) free_html_label(p->u.html, 1);
- } else {
- free_textspan(p->u.txt.span, p->u.txt.nspans);
- }
- free(p);
- }
- }
- void emit_label(GVJ_t * job, emit_state_t emit_state, textlabel_t * lp)
- {
- obj_state_t *obj = job->obj;
- pointf p;
- emit_state_t old_emit_state;
- old_emit_state = obj->emit_state;
- obj->emit_state = emit_state;
- if (lp->html) {
- emit_html_label(job, lp->u.html, lp);
- obj->emit_state = old_emit_state;
- return;
- }
- /* make sure that there is something to do */
- if (lp->u.txt.nspans < 1)
- return;
- gvrender_begin_label(job, LABEL_PLAIN);
- gvrender_set_pencolor(job, lp->fontcolor);
- /* position for first span */
- switch (lp->valign) {
- case 't':
- p.y = lp->pos.y + lp->space.y / 2.0 - lp->fontsize;
- break;
- case 'b':
- p.y = lp->pos.y - lp->space.y / 2.0 + lp->dimen.y - lp->fontsize;
- break;
- case 'c':
- default:
- p.y = lp->pos.y + lp->dimen.y / 2.0 - lp->fontsize;
- break;
- }
- if (obj->labeledgealigned)
- p.y -= lp->pos.y;
- for (size_t i = 0; i < lp->u.txt.nspans; i++) {
- switch (lp->u.txt.span[i].just) {
- case 'l':
- p.x = lp->pos.x - lp->space.x / 2.0;
- break;
- case 'r':
- p.x = lp->pos.x + lp->space.x / 2.0;
- break;
- default:
- case 'n':
- p.x = lp->pos.x;
- break;
- }
- gvrender_textspan(job, p, &(lp->u.txt.span[i]));
- /* UL position for next span */
- p.y -= lp->u.txt.span[i].size.y;
- }
- gvrender_end_label(job);
- obj->emit_state = old_emit_state;
- }
- /* strdup_and_subst_obj0:
- * Replace various escape sequences with the name of the associated
- * graph object. A double backslash \\ can be used to avoid a replacement.
- * If escBackslash is true, convert \\ to \; else leave alone. All other dyads
- * of the form \. are passed through unchanged.
- */
- static char *strdup_and_subst_obj0 (char *str, void *obj, int escBackslash)
- {
- char c, *s;
- char *tp_str = "", *hp_str = "";
- char *g_str = "\\G", *n_str = "\\N", *e_str = "\\E",
- *h_str = "\\H", *t_str = "\\T", *l_str = "\\L";
- bool has_hp = false;
- bool has_tp = false;
- int isEdge = 0;
- textlabel_t *tl;
- port pt;
- /* prepare substitution strings */
- switch (agobjkind(obj)) {
- case AGRAPH:
- g_str = agnameof(obj);
- tl = GD_label(obj);
- if (tl) {
- l_str = tl->text;
- }
- break;
- case AGNODE:
- g_str = agnameof(agraphof(obj));
- n_str = agnameof(obj);
- tl = ND_label(obj);
- if (tl) {
- l_str = tl->text;
- }
- break;
- case AGEDGE:
- isEdge = 1;
- g_str = agnameof(agroot(agraphof(agtail(((edge_t *)obj)))));
- t_str = agnameof(agtail(((edge_t *)obj)));
- pt = ED_tail_port(obj);
- if ((tp_str = pt.name))
- has_tp = *tp_str != '\0';
- h_str = agnameof(aghead(((edge_t *)obj)));
- pt = ED_head_port(obj);
- if ((hp_str = pt.name))
- has_hp = *hp_str != '\0';
- tl = ED_label(obj);
- if (tl) {
- l_str = tl->text;
- }
- if (agisdirected(agroot(agraphof(agtail(((edge_t*)obj))))))
- e_str = "->";
- else
- e_str = "--";
- break;
- }
- /* allocate a dynamic buffer that we will use to construct the result */
- agxbuf buf = {0};
- /* assemble new string */
- for (s = str; (c = *s++);) {
- if (c == '\\' && *s != '\0') {
- switch (c = *s++) {
- case 'G':
- agxbput(&buf, g_str);
- break;
- case 'N':
- agxbput(&buf, n_str);
- break;
- case 'E':
- if (isEdge) {
- agxbput(&buf, t_str);
- if (has_tp) {
- agxbprint(&buf, ":%s", tp_str);
- }
- agxbprint(&buf, "%s%s", e_str, h_str);
- if (has_hp) {
- agxbprint(&buf, ":%s", hp_str);
- }
- }
- break;
- case 'T':
- agxbput(&buf, t_str);
- break;
- case 'H':
- agxbput(&buf, h_str);
- break;
- case 'L':
- agxbput(&buf, l_str);
- break;
- case '\\':
- if (escBackslash) {
- agxbputc(&buf, '\\');
- break;
- }
- /* Fall through */
- default: /* leave other escape sequences unmodified, e.g. \n \l \r */
- agxbprint(&buf, "\\%c", c);
- break;
- }
- } else {
- agxbputc(&buf, c);
- }
- }
- /* extract the final string with replacements applied */
- return agxbdisown(&buf);
- }
- /* strdup_and_subst_obj:
- * Processes graph object escape sequences; also collapses \\ to \.
- */
- char *strdup_and_subst_obj(char *str, void *obj)
- {
- return strdup_and_subst_obj0 (str, obj, 1);
- }
|