xlabels.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. #include <assert.h>
  11. #include <errno.h>
  12. #include <limits.h>
  13. #include <math.h>
  14. #include <stdbool.h>
  15. #include <stdlib.h>
  16. #include <stdio.h>
  17. #include <string.h>
  18. #define XLABEL_INT
  19. #include <label/xlabels.h>
  20. #include <util/alloc.h>
  21. #include <util/exit.h>
  22. static int icompare(void *, void *);
  23. Dtdisc_t Hdisc = { offsetof(HDict_t, key), sizeof(int), -1, 0, 0,
  24. icompare
  25. };
  26. static int icompare(void *v1, void *v2) {
  27. int k1 = *((int *) v1), k2 = *((int *) v2);
  28. if (k1 < k2) {
  29. return -1;
  30. }
  31. if (k1 > k2) {
  32. return 1;
  33. }
  34. return 0;
  35. }
  36. static XLabels_t *xlnew(object_t *objs, size_t n_objs, xlabel_t *lbls,
  37. size_t n_lbls, label_params_t *params) {
  38. XLabels_t *xlp = gv_alloc(sizeof(XLabels_t));
  39. /* used to load the rtree in hilbert space filling curve order */
  40. if (!(xlp->hdx = dtopen(&Hdisc, Dtobag))) {
  41. fprintf(stderr, "out of memory\n");
  42. graphviz_exit(EXIT_FAILURE);
  43. }
  44. /* for querying intersection candidates */
  45. if (!(xlp->spdx = RTreeOpen())) {
  46. fprintf(stderr, "out of memory\n");
  47. graphviz_exit(EXIT_FAILURE);
  48. }
  49. /* save arg pointers in the handle */
  50. xlp->objs = objs;
  51. xlp->n_objs = n_objs;
  52. xlp->lbls = lbls;
  53. xlp->n_lbls = n_lbls;
  54. xlp->params = params;
  55. return xlp;
  56. }
  57. static void xlfree(XLabels_t * xlp)
  58. {
  59. RTreeClose(xlp->spdx);
  60. free(xlp);
  61. }
  62. /***************************************************************************/
  63. /*
  64. * determine the order(depth) of the hilbert sfc so that we satisfy the
  65. * precondition of hd_hil_s_from_xy()
  66. */
  67. static unsigned int xlhorder(XLabels_t * xlp)
  68. {
  69. double maxx = xlp->params->bb.UR.x, maxy = xlp->params->bb.UR.y;
  70. return (unsigned)floor(log2(round(fmax(maxx, maxy)))) + 1;
  71. }
  72. /* from http://www.hackersdelight.org/ site for the book by Henry S Warren */
  73. /*
  74. * precondition
  75. * pow(2, n) >= max(p.x, p.y)
  76. */
  77. /* adapted from lams1.c
  78. Given the "order" n of a Hilbert curve and coordinates x and y, this
  79. program computes the length s of the curve from the origin to (x, y).
  80. The square that the Hilbert curve traverses is of size 2**n by 2**n.
  81. The method is that given in [Lam&Shap], described by the following
  82. table. Here i = n-1 for the most significant bit of x and y, and i = 0
  83. for the least significant bits.
  84. x[i] y[i] | s[2i+1:2i] x y
  85. -----------|-------------------
  86. 0 0 | 00 y x
  87. 0 1 | 01 x y
  88. 1 0 | 11 ~y ~x
  89. 1 1 | 10 x y
  90. To use this table, start at the most significant bits of x and y
  91. (i = n - 1). If they are both 0 (first row), set the most significant
  92. two bits of s to 00 and interchange x and y. (Actually, it is only
  93. necessary to interchange the remaining bits of x and y.) If the most
  94. significant bits of x and y are 10 (third row), output 11, interchange x
  95. and y, and complement x and y.
  96. Then, consider the next most significant bits of x and y (which may
  97. have been changed by this process), and select the appropriate row of
  98. the table to determine the next two bits of s, and how to change x and
  99. y. Continue until the least significant bits of x and y have been
  100. processed. */
  101. static unsigned int hd_hil_s_from_xy(point p, int n)
  102. {
  103. int x = p.x, y = p.y;
  104. unsigned s = 0; /* Initialize. */
  105. for (int i = n - 1; i >= 0; i--) {
  106. int xi = (x >> i) & 1; /* Get bit i of x. */
  107. int yi = (y >> i) & 1; /* Get bit i of y. */
  108. s = 4 * s + 2 * (unsigned)xi + ((unsigned)xi ^ (unsigned)yi); // Append two bits to s.
  109. x = x ^ y; /* These 3 lines swap */
  110. y = y ^ (x & (yi - 1)); /* x and y if yi = 0. */
  111. x = x ^ y;
  112. x = x ^ (-xi & (yi - 1)); /* Complement x and y if */
  113. y = y ^ (-xi & (yi - 1)); /* xi = 1 and yi = 0. */
  114. }
  115. return s;
  116. }
  117. /* intersection test from
  118. * from Real-Time Collision Detection 4.2.1 by Christer Ericson
  119. * intersection area from
  120. * http://stackoverflow.com/questions/4549544/total-area-of-intersecting-rectangles
  121. */
  122. static double aabbaabb(Rect_t * r, Rect_t * s)
  123. {
  124. if (!Overlap(r, s))
  125. return 0;
  126. /* if we get here we have an intersection */
  127. /* rightmost left edge of the 2 rectangles */
  128. double iminx =
  129. r->boundary[0] > s->boundary[0] ? r->boundary[0] : s->boundary[0];
  130. /* upmost bottom edge */
  131. double iminy =
  132. r->boundary[1] > s->boundary[1] ? r->boundary[1] : s->boundary[1];
  133. /* leftmost right edge */
  134. double imaxx =
  135. r->boundary[2] < s->boundary[2] ? r->boundary[2] : s->boundary[2];
  136. /* downmost top edge */
  137. double imaxy =
  138. r->boundary[3] < s->boundary[3] ? r->boundary[3] : s->boundary[3];
  139. return (imaxx - iminx) * (imaxy - iminy);
  140. }
  141. /*
  142. * test if objp1, a size 0 object is enclosed in the xlabel
  143. * associated with objp
  144. */
  145. static bool lblenclosing(object_t *objp, object_t *objp1) {
  146. xlabel_t * xlp = objp->lbl;;
  147. assert(objp1->sz.x == 0 && objp1->sz.y == 0);
  148. if(! xlp) return false;
  149. return objp1->pos.x > xlp->pos.x &&
  150. objp1->pos.x < (xlp->pos.x + xlp->sz.x) &&
  151. objp1->pos.y > xlp->pos.y &&
  152. objp1->pos.y < (xlp->pos.y + xlp->sz.y);
  153. }
  154. /*fill in rectangle from the object */
  155. static Rect_t objp2rect(const object_t *op) {
  156. Rect_t r = {0};
  157. r.boundary[0] = op->pos.x;
  158. r.boundary[1] = op->pos.y;
  159. r.boundary[2] = op->pos.x + op->sz.x;
  160. r.boundary[3] = op->pos.y + op->sz.y;
  161. return r;
  162. }
  163. /*fill in rectangle from the objects xlabel */
  164. static Rect_t objplp2rect(const object_t *objp) {
  165. Rect_t r = {0};
  166. const xlabel_t *lp = objp->lbl;
  167. r.boundary[0] = lp->pos.x;
  168. r.boundary[1] = lp->pos.y;
  169. r.boundary[2] = lp->pos.x + lp->sz.x;
  170. r.boundary[3] = lp->pos.y + lp->sz.y;
  171. return r;
  172. }
  173. /* compute boundary that encloses all possible label boundaries */
  174. static Rect_t objplpmks(object_t * objp)
  175. {
  176. Rect_t rect;
  177. pointf p;
  178. p.x = p.y = 0.0;
  179. if (objp->lbl)
  180. p = objp->lbl->sz;
  181. rect.boundary[0] = (int) floor(objp->pos.x - p.x);
  182. rect.boundary[1] = (int) floor(objp->pos.y - p.y);
  183. rect.boundary[2] = (int) ceil(objp->pos.x + objp->sz.x + p.x);
  184. assert(rect.boundary[2] < INT_MAX);
  185. rect.boundary[3] = (int) ceil(objp->pos.y + objp->sz.y + p.y);
  186. assert(rect.boundary[3] < INT_MAX);
  187. return rect;
  188. }
  189. /* determine the position clp will occupy in intrsx[] */
  190. static int getintrsxi(object_t * op, object_t * cp)
  191. {
  192. xlabel_t *lp = op->lbl, *clp = cp->lbl;
  193. assert(lp != clp);
  194. if (lp->set == 0 || clp->set == 0)
  195. return -1;
  196. if ((op->pos.x == 0.0 && op->pos.y == 0.0) ||
  197. (cp->pos.x == 0.0 && cp->pos.y == 0.0))
  198. return -1;
  199. if (cp->pos.y < op->pos.y) {
  200. if (cp->pos.x < op->pos.x)
  201. return XLPXPY;
  202. if (cp->pos.x > op->pos.x)
  203. return XLNXPY;
  204. return XLCXPY;
  205. }
  206. if (cp->pos.y > op->pos.y) {
  207. if (cp->pos.x < op->pos.x)
  208. return XLPXNY;
  209. if (cp->pos.x > op->pos.x)
  210. return XLNXNY;
  211. return XLCXNY;
  212. }
  213. if (cp->pos.x < op->pos.x)
  214. return XLPXCY;
  215. if (cp->pos.x > op->pos.x)
  216. return XLNXCY;
  217. return -1;
  218. }
  219. /* record the intersecting objects label */
  220. static double
  221. recordointrsx(object_t * op, object_t * cp, Rect_t * rp,
  222. double a, object_t * intrsx[XLNBR])
  223. {
  224. int i = getintrsxi(op, cp);
  225. if (i < 0)
  226. i = 5;
  227. if (intrsx[i] != NULL) {
  228. double sa, maxa = 0.0;
  229. /* keep maximally overlapping object */
  230. Rect_t srect = objp2rect(intrsx[i]);
  231. sa = aabbaabb(rp, &srect);
  232. if (sa > a)
  233. maxa = sa;
  234. /*keep maximally overlapping label */
  235. if (intrsx[i]->lbl) {
  236. srect = objplp2rect(intrsx[i]);
  237. sa = aabbaabb(rp, &srect);
  238. if (sa > a)
  239. maxa = fmax(sa, maxa);
  240. }
  241. if (maxa > 0.0)
  242. return maxa;
  243. /*replace overlapping label/object pair */
  244. intrsx[i] = cp;
  245. return a;
  246. }
  247. intrsx[i] = cp;
  248. return a;
  249. }
  250. /* record the intersecting label */
  251. static double
  252. recordlintrsx(object_t * op, object_t * cp, Rect_t * rp,
  253. double a, object_t * intrsx[XLNBR])
  254. {
  255. int i = getintrsxi(op, cp);
  256. if (i < 0)
  257. i = 5;
  258. if (intrsx[i] != NULL) {
  259. double sa, maxa = 0.0;
  260. /* keep maximally overlapping object */
  261. Rect_t srect = objp2rect(intrsx[i]);
  262. sa = aabbaabb(rp, &srect);
  263. if (sa > a)
  264. maxa = sa;
  265. /*keep maximally overlapping label */
  266. if (intrsx[i]->lbl) {
  267. srect = objplp2rect(intrsx[i]);
  268. sa = aabbaabb(rp, &srect);
  269. if (sa > a)
  270. maxa = fmax(sa, maxa);
  271. }
  272. if (maxa > 0.0)
  273. return maxa;
  274. /*replace overlapping label/object pair */
  275. intrsx[i] = cp;
  276. return a;
  277. }
  278. intrsx[i] = cp;
  279. return a;
  280. }
  281. /* find the objects and labels intersecting lp */
  282. static BestPos_t
  283. xlintersections(XLabels_t * xlp, object_t * objp, object_t * intrsx[XLNBR])
  284. {
  285. BestPos_t bp;
  286. assert(objp->lbl);
  287. bp.n = 0;
  288. bp.area = 0.0;
  289. bp.pos = objp->lbl->pos;
  290. for (size_t i = 0; i < xlp->n_objs; i++) {
  291. if(objp == &xlp->objs[i]) continue;
  292. if(xlp->objs[i].sz.x > 0 && xlp->objs[i].sz.y > 0) continue;
  293. if(lblenclosing(objp, &xlp->objs[i]) ) {
  294. bp.n++;
  295. }
  296. }
  297. Rect_t rect = objplp2rect(objp);
  298. LeafList_t *llp = RTreeSearch(xlp->spdx, xlp->spdx->root, &rect);
  299. if (!llp)
  300. return bp;
  301. for (LeafList_t *ilp = llp; ilp; ilp = ilp->next) {
  302. double a, ra;
  303. object_t *cp = ilp->leaf->data;
  304. if (cp == objp)
  305. continue;
  306. /*label-object intersect */
  307. Rect_t srect = objp2rect(cp);
  308. a = aabbaabb(&rect, &srect);
  309. if (a > 0.0) {
  310. ra = recordointrsx(objp, cp, &rect, a, intrsx);
  311. bp.n++;
  312. bp.area += ra;
  313. }
  314. /*label-label intersect */
  315. if (!cp->lbl || !cp->lbl->set)
  316. continue;
  317. srect = objplp2rect(cp);
  318. a = aabbaabb(&rect, &srect);
  319. if (a > 0.0) {
  320. ra = recordlintrsx(objp, cp, &rect, a, intrsx);
  321. bp.n++;
  322. bp.area += ra;
  323. }
  324. }
  325. RTreeLeafListFree(llp);
  326. return bp;
  327. }
  328. /*
  329. * xladjust - find a label position
  330. * the individual tests at the top are intended to place a preference order
  331. * on the position
  332. */
  333. static BestPos_t xladjust(XLabels_t * xlp, object_t * objp)
  334. {
  335. xlabel_t *lp = objp->lbl;
  336. double xincr = (2 * lp->sz.x + objp->sz.x) / XLXDENOM;
  337. double yincr = (2 * lp->sz.y + objp->sz.y) / XLYDENOM;
  338. object_t *intrsx[XLNBR] = {0};
  339. BestPos_t bp, nbp;
  340. assert(objp->lbl);
  341. /*x left */
  342. lp->pos.x = objp->pos.x - lp->sz.x;
  343. /*top */
  344. lp->pos.y = objp->pos.y + objp->sz.y;
  345. bp = xlintersections(xlp, objp, intrsx);
  346. if (bp.n == 0)
  347. return bp;
  348. /*mid */
  349. lp->pos.y = objp->pos.y;
  350. nbp = xlintersections(xlp, objp, intrsx);
  351. if (nbp.n == 0)
  352. return nbp;
  353. if (nbp.area < bp.area)
  354. bp = nbp;
  355. /*bottom */
  356. lp->pos.y = objp->pos.y - lp->sz.y;
  357. nbp = xlintersections(xlp, objp, intrsx);
  358. if (nbp.n == 0)
  359. return nbp;
  360. if (nbp.area < bp.area)
  361. bp = nbp;
  362. /*x mid */
  363. lp->pos.x = objp->pos.x;
  364. /*top */
  365. lp->pos.y = objp->pos.y + objp->sz.y;
  366. nbp = xlintersections(xlp, objp, intrsx);
  367. if (nbp.n == 0)
  368. return nbp;
  369. if (nbp.area < bp.area)
  370. bp = nbp;
  371. /*bottom */
  372. lp->pos.y = objp->pos.y - lp->sz.y;
  373. nbp = xlintersections(xlp, objp, intrsx);
  374. if (nbp.n == 0)
  375. return nbp;
  376. if (nbp.area < bp.area)
  377. bp = nbp;
  378. /*x right */
  379. lp->pos.x = objp->pos.x + objp->sz.x;
  380. /*top */
  381. lp->pos.y = objp->pos.y + objp->sz.y;
  382. nbp = xlintersections(xlp, objp, intrsx);
  383. if (nbp.n == 0)
  384. return nbp;
  385. if (nbp.area < bp.area)
  386. bp = nbp;
  387. /*mid */
  388. lp->pos.y = objp->pos.y;
  389. nbp = xlintersections(xlp, objp, intrsx);
  390. if (nbp.n == 0)
  391. return nbp;
  392. if (nbp.area < bp.area)
  393. bp = nbp;
  394. /*bottom */
  395. lp->pos.y = objp->pos.y - lp->sz.y;
  396. nbp = xlintersections(xlp, objp, intrsx);
  397. if (nbp.n == 0)
  398. return nbp;
  399. if (nbp.area < bp.area)
  400. bp = nbp;
  401. /*sliding from top left */
  402. if (intrsx[XLPXNY] || intrsx[XLCXNY] || intrsx[XLNXNY] || intrsx[XLPXCY] || intrsx[XLPXPY]) { /* have to move */
  403. if (!intrsx[XLCXNY] && !intrsx[XLNXNY]) { /* some room right? */
  404. /* slide along upper edge */
  405. for (lp->pos.x = objp->pos.x - lp->sz.x,
  406. lp->pos.y = objp->pos.y + objp->sz.y;
  407. lp->pos.x <= (objp->pos.x + objp->sz.x);
  408. lp->pos.x += xincr) {
  409. nbp = xlintersections(xlp, objp, intrsx);
  410. if (nbp.n == 0)
  411. return nbp;
  412. if (nbp.area < bp.area)
  413. bp = nbp;
  414. }
  415. }
  416. if (!intrsx[XLPXCY] && !intrsx[XLPXPY]) { /* some room down? */
  417. /* slide down left edge */
  418. for (lp->pos.x = objp->pos.x - lp->sz.x,
  419. lp->pos.y = objp->pos.y + objp->sz.y;
  420. lp->pos.y >= (objp->pos.y - lp->sz.y);
  421. lp->pos.y -= yincr) {
  422. nbp = xlintersections(xlp, objp, intrsx);
  423. if (nbp.n == 0)
  424. return nbp;
  425. if (nbp.area < bp.area)
  426. bp = nbp;
  427. }
  428. }
  429. }
  430. /*sliding from bottom right */
  431. lp->pos.x = objp->pos.x + objp->sz.x;
  432. lp->pos.y = objp->pos.y - lp->sz.y;
  433. if (intrsx[XLNXPY] || intrsx[XLCXPY] || intrsx[XLPXPY] || intrsx[XLNXCY] || intrsx[XLNXNY]) { /* have to move */
  434. if (!intrsx[XLCXPY] && !intrsx[XLPXPY]) { /* some room left? */
  435. /* slide along lower edge */
  436. for (lp->pos.x = objp->pos.x + objp->sz.x,
  437. lp->pos.y = objp->pos.y - lp->sz.y;
  438. lp->pos.x >= (objp->pos.x - lp->sz.x);
  439. lp->pos.x -= xincr) {
  440. nbp = xlintersections(xlp, objp, intrsx);
  441. if (nbp.n == 0)
  442. return nbp;
  443. if (nbp.area < bp.area)
  444. bp = nbp;
  445. }
  446. }
  447. if (!intrsx[XLNXCY] && !intrsx[XLNXNY]) { /* some room up? */
  448. /* slide up right edge */
  449. for (lp->pos.x = objp->pos.x + objp->sz.x,
  450. lp->pos.y = objp->pos.y - lp->sz.y;
  451. lp->pos.y <= (objp->pos.y + objp->sz.y);
  452. lp->pos.y += yincr) {
  453. nbp = xlintersections(xlp, objp, intrsx);
  454. if (nbp.n == 0)
  455. return nbp;
  456. if (nbp.area < bp.area)
  457. bp = nbp;
  458. }
  459. }
  460. }
  461. return bp;
  462. }
  463. /* load the hilbert sfc keyed tree */
  464. static int xlhdxload(XLabels_t * xlp)
  465. {
  466. int order = xlhorder(xlp);
  467. for (size_t i = 0; i < xlp->n_objs; i++) {
  468. HDict_t *hp;
  469. point pi;
  470. hp = gv_alloc(sizeof(HDict_t));
  471. hp->d.data = &xlp->objs[i];
  472. hp->d.rect = objplpmks(&xlp->objs[i]);
  473. /* center of the labeling area */
  474. pi.x = hp->d.rect.boundary[0] +
  475. (hp->d.rect.boundary[2] - hp->d.rect.boundary[0]) / 2;
  476. pi.y = hp->d.rect.boundary[1] +
  477. (hp->d.rect.boundary[3] - hp->d.rect.boundary[1]) / 2;
  478. hp->key = hd_hil_s_from_xy(pi, order);
  479. if (!dtinsert(xlp->hdx, hp))
  480. return -1;
  481. }
  482. return 0;
  483. }
  484. static void xlhdxunload(XLabels_t * xlp)
  485. {
  486. int size=dtsize(xlp->hdx), freed=0;
  487. while(dtsize(xlp->hdx) ) {
  488. void*vp=dtfinger(xlp->hdx);
  489. assert(vp);
  490. if(vp) {
  491. dtdetach(xlp->hdx, vp);
  492. free(vp);
  493. freed++;
  494. }
  495. }
  496. assert(size==freed);
  497. (void)size;
  498. }
  499. static void xlspdxload(XLabels_t *xlp) {
  500. for (HDict_t *op = dtfirst(xlp->hdx); op; op = dtnext(xlp->hdx, op)) {
  501. /* tree rectangle data node lvl */
  502. RTreeInsert(xlp->spdx, &op->d.rect, op->d.data, &xlp->spdx->root, 0);
  503. }
  504. }
  505. static int xlinitialize(XLabels_t * xlp)
  506. {
  507. int r=0;
  508. if ((r = xlhdxload(xlp)) < 0)
  509. return r;
  510. xlspdxload(xlp);
  511. xlhdxunload(xlp);
  512. return dtclose(xlp->hdx);
  513. }
  514. int placeLabels(object_t *objs, size_t n_objs, xlabel_t *lbls, size_t n_lbls,
  515. label_params_t *params) {
  516. int r;
  517. BestPos_t bp;
  518. XLabels_t *xlp = xlnew(objs, n_objs, lbls, n_lbls, params);
  519. if ((r = xlinitialize(xlp)) < 0)
  520. return r;
  521. /* Place xlabel_t* lp near lp->obj so that the rectangle whose lower-left
  522. * corner is lp->pos, and size is lp->sz does not intersect any object
  523. * in objs (by convention, an object consisting of a single point
  524. * intersects nothing) nor any other label, if possible. On input,
  525. * lp->set is 0.
  526. *
  527. * On output, any label with a position should have this stored in
  528. * lp->pos and have lp->set non-zero.
  529. *
  530. * If params->force is true, all labels must be positioned, even if
  531. * overlaps are necessary.
  532. *
  533. * Return 0 if all labels could be placed without overlap;
  534. * non-zero otherwise.
  535. */
  536. r = 0;
  537. for (size_t i = 0; i < n_objs; i++) {
  538. if (objs[i].lbl == 0)
  539. continue;
  540. bp = xladjust(xlp, &objs[i]);
  541. if (bp.n == 0) {
  542. objs[i].lbl->set = 1;
  543. } else if(bp.area == 0) {
  544. objs[i].lbl->pos.x = bp.pos.x;
  545. objs[i].lbl->pos.y = bp.pos.y;
  546. objs[i].lbl->set = 1;
  547. } else if (params->force == 1) {
  548. objs[i].lbl->pos.x = bp.pos.x;
  549. objs[i].lbl->pos.y = bp.pos.y;
  550. objs[i].lbl->set = 1;
  551. } else {
  552. r = 1;
  553. }
  554. }
  555. xlfree(xlp);
  556. return r;
  557. }