htmltable.c 51 KB

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