labels.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /// @file
  2. /// @ingroup common_render
  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 v1.0
  7. * which accompanies this distribution, and is available at
  8. * https://www.eclipse.org/legal/epl-v10.html
  9. *
  10. * Contributors: Details at https://graphviz.org
  11. *************************************************************************/
  12. #include <common/render.h>
  13. #include <common/htmltable.h>
  14. #include <limits.h>
  15. #include <stdbool.h>
  16. #include <stddef.h>
  17. #include <util/agxbuf.h>
  18. #include <util/alloc.h>
  19. static char *strdup_and_subst_obj0 (char *str, void *obj, int escBackslash);
  20. static void storeline(GVC_t *gvc, textlabel_t *lp, char *line,
  21. char terminator) {
  22. pointf size;
  23. textspan_t *span;
  24. size_t oldsz = lp->u.txt.nspans + 1;
  25. lp->u.txt.span = gv_recalloc(lp->u.txt.span, oldsz, oldsz + 1,
  26. sizeof(textspan_t));
  27. span = &lp->u.txt.span[lp->u.txt.nspans];
  28. span->str = line;
  29. span->just = terminator;
  30. if (line && line[0]) {
  31. textfont_t tf = {0};
  32. tf.name = lp->fontname;
  33. tf.size = lp->fontsize;
  34. span->font = dtinsert(gvc->textfont_dt, &tf);
  35. size = textspan_size(gvc, span);
  36. }
  37. else {
  38. size.x = 0.0;
  39. span->size.y = size.y = (int)(lp->fontsize * LINESPACING);
  40. }
  41. lp->u.txt.nspans++;
  42. /* width = max line width */
  43. lp->dimen.x = MAX(lp->dimen.x, size.x);
  44. /* accumulate height */
  45. lp->dimen.y += size.y;
  46. }
  47. /* compiles <str> into a label <lp> */
  48. void make_simple_label(GVC_t * gvc, textlabel_t * lp)
  49. {
  50. lp->dimen.x = lp->dimen.y = 0.0;
  51. if (*lp->text == '\0')
  52. return;
  53. agxbuf line = {0};
  54. for (char c, *p = lp->text; (c = *p++);) {
  55. unsigned char byte = (unsigned char) c;
  56. /* wingraphviz allows a combination of ascii and big-5. The latter
  57. * is a two-byte encoding, with the first byte in 0xA1-0xFE, and
  58. * the second in 0x40-0x7e or 0xa1-0xfe. We assume that the input
  59. * is well-formed, but check that we don't go past the ending '\0'.
  60. */
  61. if (lp->charset == CHAR_BIG5 && 0xA1 <= byte && byte <= 0xFE) {
  62. agxbputc(&line, c);
  63. c = *p++;
  64. agxbputc(&line, c);
  65. if (!c) /* NB. Protect against unexpected string end here */
  66. break;
  67. } else {
  68. if (c == '\\') {
  69. switch (*p) {
  70. case 'n':
  71. case 'l':
  72. case 'r':
  73. storeline(gvc, lp, agxbdisown(&line), *p);
  74. break;
  75. default:
  76. agxbputc(&line, *p);
  77. }
  78. if (*p)
  79. p++;
  80. /* tcldot can enter real linend characters */
  81. } else if (c == '\n') {
  82. storeline(gvc, lp, agxbdisown(&line), 'n');
  83. } else {
  84. agxbputc(&line, c);
  85. }
  86. }
  87. }
  88. if (agxblen(&line) > 0) {
  89. storeline(gvc, lp, agxbdisown(&line), 'n');
  90. }
  91. agxbfree(&line);
  92. lp->space = lp->dimen;
  93. }
  94. /* make_label:
  95. * Assume str is freshly allocated for this instance, so it
  96. * can be freed in free_label.
  97. */
  98. textlabel_t *make_label(void *obj, char *str, int kind, double fontsize, char *fontname, char *fontcolor)
  99. {
  100. textlabel_t *rv = gv_alloc(sizeof(textlabel_t));
  101. graph_t *g = NULL, *sg = NULL;
  102. node_t *n = NULL;
  103. edge_t *e = NULL;
  104. char *s;
  105. switch (agobjkind(obj)) {
  106. case AGRAPH:
  107. sg = obj;
  108. g = sg->root;
  109. break;
  110. case AGNODE:
  111. n = obj;
  112. g = agroot(agraphof(n));
  113. break;
  114. case AGEDGE:
  115. e = obj;
  116. g = agroot(agraphof(aghead(e)));
  117. break;
  118. }
  119. rv->fontname = fontname;
  120. rv->fontcolor = fontcolor;
  121. rv->fontsize = fontsize;
  122. rv->charset = GD_charset(g);
  123. if (kind & LT_RECD) {
  124. rv->text = gv_strdup(str);
  125. if (kind & LT_HTML) {
  126. rv->html = true;
  127. }
  128. }
  129. else if (kind == LT_HTML) {
  130. rv->text = gv_strdup(str);
  131. rv->html = true;
  132. if (make_html_label(obj, rv)) {
  133. switch (agobjkind(obj)) {
  134. case AGRAPH:
  135. agerr(AGPREV, "in label of graph %s\n",agnameof(sg));
  136. break;
  137. case AGNODE:
  138. agerr(AGPREV, "in label of node %s\n", agnameof(n));
  139. break;
  140. case AGEDGE:
  141. agerr(AGPREV, "in label of edge %s %s %s\n",
  142. agnameof(agtail(e)), agisdirected(g)?"->":"--", agnameof(aghead(e)));
  143. break;
  144. }
  145. }
  146. }
  147. else {
  148. assert(kind == LT_NONE);
  149. /* This call just processes the graph object based escape sequences. The formatting escape
  150. * sequences (\n, \l, \r) are processed in make_simple_label. That call also replaces \\ with \.
  151. */
  152. rv->text = strdup_and_subst_obj0(str, obj, 0);
  153. switch (rv->charset) {
  154. case CHAR_LATIN1:
  155. s = latin1ToUTF8(rv->text);
  156. break;
  157. default: /* UTF8 */
  158. s = htmlEntityUTF8(rv->text, g);
  159. break;
  160. }
  161. free(rv->text);
  162. rv->text = s;
  163. make_simple_label(GD_gvc(g), rv);
  164. }
  165. return rv;
  166. }
  167. /* free_textspan:
  168. * Free resources related to textspan_t.
  169. * tl is an array of cnt textspan_t's.
  170. * It is also assumed that the text stored in the str field
  171. * is all stored in one large buffer shared by all of the textspan_t,
  172. * so only the first one needs to free its tlp->str.
  173. */
  174. void free_textspan(textspan_t *tl, size_t cnt) {
  175. textspan_t* tlp = tl;
  176. if (!tl) return;
  177. for (size_t i = 0; i < cnt; i++) {
  178. free(tlp->str);
  179. if (tlp->layout && tlp->free_layout)
  180. tlp->free_layout (tlp->layout);
  181. tlp++;
  182. }
  183. free(tl);
  184. }
  185. void free_label(textlabel_t * p)
  186. {
  187. if (p) {
  188. free(p->text);
  189. if (p->html) {
  190. if (p->u.html) free_html_label(p->u.html, 1);
  191. } else {
  192. free_textspan(p->u.txt.span, p->u.txt.nspans);
  193. }
  194. free(p);
  195. }
  196. }
  197. void emit_label(GVJ_t * job, emit_state_t emit_state, textlabel_t * lp)
  198. {
  199. obj_state_t *obj = job->obj;
  200. pointf p;
  201. emit_state_t old_emit_state;
  202. old_emit_state = obj->emit_state;
  203. obj->emit_state = emit_state;
  204. if (lp->html) {
  205. emit_html_label(job, lp->u.html, lp);
  206. obj->emit_state = old_emit_state;
  207. return;
  208. }
  209. /* make sure that there is something to do */
  210. if (lp->u.txt.nspans < 1)
  211. return;
  212. gvrender_begin_label(job, LABEL_PLAIN);
  213. gvrender_set_pencolor(job, lp->fontcolor);
  214. /* position for first span */
  215. switch (lp->valign) {
  216. case 't':
  217. p.y = lp->pos.y + lp->space.y / 2.0 - lp->fontsize;
  218. break;
  219. case 'b':
  220. p.y = lp->pos.y - lp->space.y / 2.0 + lp->dimen.y - lp->fontsize;
  221. break;
  222. case 'c':
  223. default:
  224. p.y = lp->pos.y + lp->dimen.y / 2.0 - lp->fontsize;
  225. break;
  226. }
  227. if (obj->labeledgealigned)
  228. p.y -= lp->pos.y;
  229. for (size_t i = 0; i < lp->u.txt.nspans; i++) {
  230. switch (lp->u.txt.span[i].just) {
  231. case 'l':
  232. p.x = lp->pos.x - lp->space.x / 2.0;
  233. break;
  234. case 'r':
  235. p.x = lp->pos.x + lp->space.x / 2.0;
  236. break;
  237. default:
  238. case 'n':
  239. p.x = lp->pos.x;
  240. break;
  241. }
  242. gvrender_textspan(job, p, &(lp->u.txt.span[i]));
  243. /* UL position for next span */
  244. p.y -= lp->u.txt.span[i].size.y;
  245. }
  246. gvrender_end_label(job);
  247. obj->emit_state = old_emit_state;
  248. }
  249. /* strdup_and_subst_obj0:
  250. * Replace various escape sequences with the name of the associated
  251. * graph object. A double backslash \\ can be used to avoid a replacement.
  252. * If escBackslash is true, convert \\ to \; else leave alone. All other dyads
  253. * of the form \. are passed through unchanged.
  254. */
  255. static char *strdup_and_subst_obj0 (char *str, void *obj, int escBackslash)
  256. {
  257. char c, *s;
  258. char *tp_str = "", *hp_str = "";
  259. char *g_str = "\\G", *n_str = "\\N", *e_str = "\\E",
  260. *h_str = "\\H", *t_str = "\\T", *l_str = "\\L";
  261. bool has_hp = false;
  262. bool has_tp = false;
  263. int isEdge = 0;
  264. textlabel_t *tl;
  265. port pt;
  266. /* prepare substitution strings */
  267. switch (agobjkind(obj)) {
  268. case AGRAPH:
  269. g_str = agnameof(obj);
  270. tl = GD_label(obj);
  271. if (tl) {
  272. l_str = tl->text;
  273. }
  274. break;
  275. case AGNODE:
  276. g_str = agnameof(agraphof(obj));
  277. n_str = agnameof(obj);
  278. tl = ND_label(obj);
  279. if (tl) {
  280. l_str = tl->text;
  281. }
  282. break;
  283. case AGEDGE:
  284. isEdge = 1;
  285. g_str = agnameof(agroot(agraphof(agtail(((edge_t *)obj)))));
  286. t_str = agnameof(agtail(((edge_t *)obj)));
  287. pt = ED_tail_port(obj);
  288. if ((tp_str = pt.name))
  289. has_tp = *tp_str != '\0';
  290. h_str = agnameof(aghead(((edge_t *)obj)));
  291. pt = ED_head_port(obj);
  292. if ((hp_str = pt.name))
  293. has_hp = *hp_str != '\0';
  294. tl = ED_label(obj);
  295. if (tl) {
  296. l_str = tl->text;
  297. }
  298. if (agisdirected(agroot(agraphof(agtail(((edge_t*)obj))))))
  299. e_str = "->";
  300. else
  301. e_str = "--";
  302. break;
  303. }
  304. /* allocate a dynamic buffer that we will use to construct the result */
  305. agxbuf buf = {0};
  306. /* assemble new string */
  307. for (s = str; (c = *s++);) {
  308. if (c == '\\' && *s != '\0') {
  309. switch (c = *s++) {
  310. case 'G':
  311. agxbput(&buf, g_str);
  312. break;
  313. case 'N':
  314. agxbput(&buf, n_str);
  315. break;
  316. case 'E':
  317. if (isEdge) {
  318. agxbput(&buf, t_str);
  319. if (has_tp) {
  320. agxbprint(&buf, ":%s", tp_str);
  321. }
  322. agxbprint(&buf, "%s%s", e_str, h_str);
  323. if (has_hp) {
  324. agxbprint(&buf, ":%s", hp_str);
  325. }
  326. }
  327. break;
  328. case 'T':
  329. agxbput(&buf, t_str);
  330. break;
  331. case 'H':
  332. agxbput(&buf, h_str);
  333. break;
  334. case 'L':
  335. agxbput(&buf, l_str);
  336. break;
  337. case '\\':
  338. if (escBackslash) {
  339. agxbputc(&buf, '\\');
  340. break;
  341. }
  342. /* Fall through */
  343. default: /* leave other escape sequences unmodified, e.g. \n \l \r */
  344. agxbprint(&buf, "\\%c", c);
  345. break;
  346. }
  347. } else {
  348. agxbputc(&buf, c);
  349. }
  350. }
  351. /* extract the final string with replacements applied */
  352. return agxbdisown(&buf);
  353. }
  354. /* strdup_and_subst_obj:
  355. * Processes graph object escape sequences; also collapses \\ to \.
  356. */
  357. char *strdup_and_subst_obj(char *str, void *obj)
  358. {
  359. return strdup_and_subst_obj0 (str, obj, 1);
  360. }