1 /* Dia -- an diagram creation/manipulation program
2  * Support for computing bounding boxes
3  * Copyright (C) 2001 Cyrille Chepelov
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 /** \file boundingbox.c This code is nothing but a fancy pile of FPU crap. */
21 
22 #include <config.h>
23 
24 #define _BSD_SOURCE 1
25 #include <math.h>
26 #include <string.h> /* memcmp() */
27 
28 #include <glib.h>
29 
30 #include "geometry.h"
31 #include "boundingbox.h"
32 
33 /** Translates x- or y- part of bezier points to Bernstein polynom coefficients
34  * @param p x- or y-part of the four points
35  * @param A
36  * @param B
37  * @param C
38  * @param D
39  * See: Foley et al., Computer Graphics, Bezier Curves or
40  * http://en.wikipedia.org/wiki/B%C3%A9zier_curve
41  */
42 void
bernstein_develop(const real p[4],real * A,real * B,real * C,real * D)43 bernstein_develop(const real p[4], real *A, real *B, real *C, real *D)
44 {
45   *A =   -p[0]+3*p[1]-3*p[2]+p[3];
46   *B =  3*p[0]-6*p[1]+3*p[2];
47   *C = -3*p[0]+3*p[1];
48   *D =    p[0];
49   /* if Q(u)=Sum(i=0..3)piBi(u) (Bi(u) being the Bernstein stuff),
50      then Q(u)=Au^3+Bu^2+Cu+p[0]. */
51 }
52 
53 /** Evaluates the Bernstein polynoms for a given position
54  * @param p x- or y-values of four points describing the bezier
55  * @param u position on the curve [0 .. 1]
56  * @returns the evaluate x- or y-part of the point
57  */
58 real
bezier_eval(const real p[4],real u)59 bezier_eval(const real p[4], real u)
60 {
61   real A,B,C,D;
62   bernstein_develop(p,&A,&B,&C,&D);
63   return A*u*u*u+B*u*u+C*u+D;
64 }
65 
66 /** Calculates the tangent for a given point on a bezier curve
67  * @param p x- or y-values of four points describing the bezier
68  * @param u position on the curve between[0 .. 1]
69  * @return the x- or y-part of the tangent vector
70  */
71 real
bezier_eval_tangent(const real p[4],real u)72 bezier_eval_tangent(const real p[4], real u)
73 {
74   real A,B,C,D;
75   bernstein_develop(p,&A,&B,&C,&D);
76   return 3*A*u*u+2*B*u+C;
77 }
78 
79 /**
80  * Calculates the extrma of the given curve in x- or y-direction.
81  * @param p x- or y-values of four points describing the bezier
82  * @param u The position of the extrema [0 .. 1]
83  * @return The number of extrema found.
84  */
85 static int
bicubicbezier_extrema(const real p[4],real u[2])86 bicubicbezier_extrema(const real p[4],real u[2])
87 {
88   real A,B,C,D,delta;
89 
90   bernstein_develop(p,&A,&B,&C,&D);
91   delta = 4*B*B - 12*A*C;
92 
93   u[0] = u[1] = 0.0;
94   if (delta<0) return 0;
95 
96   /* just a quadratic contribution? */
97   if (fabs(A) < 1e-6) {
98     u[0] = -C/(2*B);
99     return 1;
100   }
101 
102   u[0] = (-2*B + sqrt(delta)) / (6*A);
103   if (delta==0) return 1;
104   u[1] = (-2*B - sqrt(delta)) / (6*A);
105   return 2;
106 }
107 
108 /** Add to a bounding box the area covered by a standard arrow.
109  * @param rect The bounding box to adjust
110  * @param vertex The end point of the arrow.
111  * @param normed_dir The normalized direction of the arrow (i.e. 1 cm in the
112  * direction the arrow points from.
113  * @param extra_long ???
114  * @param extra_trans ???
115  * @bug I don't know what the last two arguments do.
116  */
117 static void
add_arrow_rectangle(Rectangle * rect,const Point * vertex,const Point * normed_dir,real extra_long,real extra_trans)118 add_arrow_rectangle(Rectangle *rect,
119                     const Point *vertex,
120                     const Point *normed_dir,
121                     real extra_long,real extra_trans)
122 {
123   Point vl,vt,pt;
124   vl = *normed_dir;
125 
126   point_get_perp(&vt,&vl);
127   point_copy_add_scaled(&pt,vertex,&vl,extra_long);
128   point_add_scaled(&pt,&vt,extra_trans);
129   rectangle_add_point(rect,&pt);
130   point_add_scaled(&pt,&vt,-2.0 * extra_trans);
131   rectangle_add_point(rect,&pt);
132   point_add_scaled(&pt,&vl,-2.0 * extra_long);
133   rectangle_add_point(rect,&pt);
134   point_add_scaled(&pt,&vt,2.0 * extra_trans);
135   rectangle_add_point(rect,&pt);
136 }
137 
138 /** Calculate the boundingbox for a 2D bezier curve segment.
139  * @param p0 start point
140  * @param p1 1st control point
141  * @param p2 2nd control point
142  * @param p3 end point
143  * @param extra information about extra space from linewidth and arrow to add to the bounding box
144  * @param rect The rectangle that the segment fits inside.
145  */
146 void
bicubicbezier2D_bbox(const Point * p0,const Point * p1,const Point * p2,const Point * p3,const PolyBBExtras * extra,Rectangle * rect)147 bicubicbezier2D_bbox(const Point *p0,const Point *p1,
148                      const Point *p2,const Point *p3,
149                      const PolyBBExtras *extra,
150                      Rectangle *rect)
151 {
152   real x[4],y[4];
153   Point vl,vt,p,tt;
154   real *xy;
155   int i,extr;
156   real u[2];
157 
158   rect->left = rect->right = p0->x;
159   rect->top = rect->bottom = p0->y;
160 
161   rectangle_add_point(rect,p3);
162   /* start point */
163   point_copy_add_scaled(&vl,p0,p1,-1);
164   point_normalize(&vl);
165   add_arrow_rectangle(rect,p0,&vl,extra->start_long,MAX(extra->start_trans,
166                                                          extra->middle_trans));
167 
168   /* end point */
169   point_copy_add_scaled(&vl,p3,p2,-1);
170   point_normalize(&vl);
171   add_arrow_rectangle(rect,p3,&vl,extra->end_long,MAX(extra->end_trans,
172                                                       extra->middle_trans));
173 
174   /* middle part */
175   x[0] = p0->x; x[1] = p1->x; x[2] = p2->x; x[3] = p3->x;
176   y[0] = p0->y; y[1] = p1->y; y[2] = p2->y; y[3] = p3->y;
177 
178   for (xy = x; xy ; xy=(xy==x?y:NULL) ) { /* sorry */
179     extr = bicubicbezier_extrema(xy,u);
180     for (i=0;i<extr;i++) {
181       if ((u[i]<0) || (u[i]>1)) continue;
182       p.x = bezier_eval(x,u[i]);
183       vl.x = bezier_eval_tangent(x,u[i]);
184       p.y = bezier_eval(y,u[i]);
185       vl.y = bezier_eval_tangent(y,u[i]);
186       point_normalize(&vl);
187       point_get_perp(&vt,&vl);
188 
189       point_copy_add_scaled(&tt,&p,&vt,extra->middle_trans);
190       rectangle_add_point(rect,&tt);
191       point_copy_add_scaled(&tt,&p,&vt,-extra->middle_trans);
192       rectangle_add_point(rect,&tt);
193     }
194   }
195 }
196 
197 /** Calculate the bounding box for a simple line.
198  * @param p1 One end of the line.
199  * @param p2 The other end of the line.
200  * @param extra Extra information
201  * @param rect The box that the line and extra stuff fits inside.
202  */
203 void
line_bbox(const Point * p1,const Point * p2,const LineBBExtras * extra,Rectangle * rect)204 line_bbox(const Point *p1, const Point *p2,
205           const LineBBExtras *extra,
206           Rectangle *rect)
207 {
208   Point vl;
209 
210   rect->left = rect->right = p1->x;
211   rect->top = rect->bottom = p1->y;
212 
213   rectangle_add_point(rect,p2); /* as a safety, so we don't need to care if it above or below p1 */
214 
215   point_copy_add_scaled(&vl,p1,p2,-1);
216   point_normalize(&vl);
217   add_arrow_rectangle(rect,p1,&vl,extra->start_long,extra->start_trans);
218   point_scale(&vl,-1);
219   add_arrow_rectangle(rect,p2,&vl,extra->end_long,extra->end_trans);
220 }
221 
222 /** Calculate the bounding box of an ellipse.
223  * @param centre The center point of the ellipse.
224  * @param width The width of the ellipse.
225  * @param height The height of the ellipse.
226  * @param extra Extra information required.
227  * @param rect The bounding box that the ellipse fits inside.
228  * @bug describe what the extra information is.
229  */
230 void
ellipse_bbox(const Point * centre,real width,real height,const ElementBBExtras * extra,Rectangle * rect)231 ellipse_bbox(const Point *centre, real width, real height,
232              const ElementBBExtras *extra,
233              Rectangle *rect)
234 {
235   Rectangle rin;
236   rin.left = centre->x - width/2;
237   rin.right = centre->x + width/2;
238   rin.top = centre->y - height/2;
239   rin.bottom = centre->y + height/2;
240 
241   rectangle_bbox(&rin,extra,rect);
242 }
243 
244 /**  Allocate some scratch space to hold a big enough Bezier.
245  * That space is not guaranteed to be preserved upon the next allocation
246  * (in fact it's guaranteed it's not).
247  * @param numpoints How many points of bezier to allocate space for.
248  * @returns Newly allocated array of points.
249  */
250 static BezPoint *
alloc_polybezier_space(int numpoints)251 alloc_polybezier_space(int numpoints)
252 {
253   static int alloc_np = 0;
254   static BezPoint *alloced = NULL;
255 
256   if (alloc_np < numpoints) {
257     g_free(alloced);
258     alloc_np = numpoints;
259     alloced = g_new0(BezPoint,numpoints);
260   }
261   return alloced;
262 }
263 
264 /** Free the scratch space allocated above.
265  * @param points Previously allocated list of points.
266  * @note Doesn't actually free it, as alloc_polybezier_space does that.
267  * @bug Should explain the strange freeing model, or fix it.
268  */
269 static void
free_polybezier_space(BezPoint * points)270 free_polybezier_space(BezPoint *points)
271 { /* dummy */ }
272 
273 /** Calculate the boundingbox for a polyline.
274  * @param pts Array of points.
275  * @param numpoints Number of elements in `pts'.
276  * @param extra Extra space information
277  * @param closed Whether the polyline is closed or not.
278  * @param rect Return value: The bounding box that includes the points and
279  * extra spacing.
280  * @bug Surely doesn't need to use bezier code, but remember extra stuff.
281  */
282 void
polyline_bbox(const Point * pts,int numpoints,const PolyBBExtras * extra,gboolean closed,Rectangle * rect)283 polyline_bbox(const Point *pts, int numpoints,
284               const PolyBBExtras *extra, gboolean closed,
285               Rectangle *rect)
286 {
287   /* It's much easier to re-use the Bezier code... */
288   int i;
289   BezPoint *bpts = alloc_polybezier_space(numpoints + 1);
290 
291   bpts[0].type = BEZ_MOVE_TO;
292   bpts[0].p1 = pts[0];
293 
294   for (i=1;i<numpoints;i++) {
295     bpts[i].type = BEZ_LINE_TO;
296     bpts[i].p1 = pts[i];
297   }
298   /* This one will be used only when closed==TRUE... */
299   bpts[numpoints].type = BEZ_LINE_TO;
300   bpts[numpoints].p1 = pts[0];
301 
302   polybezier_bbox(bpts,numpoints + (closed?1:0),extra,closed,rect);
303   free_polybezier_space(bpts);
304 }
305 
306 /** Calculate a bounding box for a set of bezier points.
307  * @param pts The bezier points
308  * @param numpoints The number of elements in `pts'
309  * @param extra Extra spacing information.
310  * @param closed True if the bezier points form a closed line.
311  * @param rect Return value: The enclosing rectangle will be stored here.
312  * @bug This function is way too long (214 lines) and should be split.
313  */
314 void
polybezier_bbox(const BezPoint * pts,int numpoints,const PolyBBExtras * extra,gboolean closed,Rectangle * rect)315 polybezier_bbox(const BezPoint *pts, int numpoints,
316                 const PolyBBExtras *extra, gboolean closed,
317                 Rectangle *rect)
318 {
319   Point vx,vn,vsc,vp;
320   int i,prev,next;
321   Rectangle rt;
322   PolyBBExtras bextra,start_bextra,end_bextra;
323   LineBBExtras lextra,start_lextra,end_lextra,full_lextra;
324   gboolean start,end;
325 
326   vp.x=0;
327   vp.y=0;
328 
329   g_assert(pts[0].type == BEZ_MOVE_TO);
330 
331   rect->left = rect->right = pts[0].p1.x;
332   rect->top = rect->bottom = pts[0].p1.y;
333 
334   /* First, we build derived BBExtras structures, so we have something to feed
335      the primitives. */
336   if (!closed) {
337     start_lextra.start_long = extra->start_long;
338     start_lextra.start_trans = MAX(extra->start_trans,extra->middle_trans);
339     start_lextra.end_long = 0;
340     start_lextra.end_trans = extra->middle_trans;
341 
342     end_lextra.start_long = 0;
343     end_lextra.start_trans = extra->middle_trans;
344     end_lextra.end_long = extra->end_long;
345     end_lextra.end_trans = MAX(extra->end_trans,extra->middle_trans);
346   }
347 
348   full_lextra.start_long = extra->start_long;
349   full_lextra.start_trans = MAX(extra->start_trans,extra->middle_trans);
350   full_lextra.end_long = extra->end_long;
351   full_lextra.end_trans = MAX(extra->end_trans,extra->middle_trans);
352 
353   if (!closed) {
354     lextra.start_long = 0;
355     lextra.start_trans = extra->middle_trans;
356     lextra.end_long = 0;
357     lextra.end_trans = extra->middle_trans;
358 
359     start_bextra.start_long = extra->start_long;
360     start_bextra.start_trans = extra->start_trans;
361     start_bextra.middle_trans = extra->middle_trans;
362     start_bextra.end_long = 0;
363     start_bextra.end_trans = extra->middle_trans;
364 
365     end_bextra.start_long = 0;
366     end_bextra.start_trans = extra->middle_trans;
367     end_bextra.middle_trans = extra->middle_trans;
368     end_bextra.end_long = extra->end_long;
369     end_bextra.end_trans = extra->end_trans;
370   }
371 
372   bextra.start_long = 0;
373   bextra.start_trans = extra->middle_trans;
374   bextra.middle_trans = extra->middle_trans;
375   bextra.end_long = 0;
376   bextra.end_trans = extra->middle_trans;
377 
378 
379   for (i=1;i<numpoints;i++) {
380     next = (i+1) % numpoints;
381     prev = (i-1) % numpoints;
382     if (closed && (next == 0)) next=1;
383     if (closed && (prev == 0)) prev=numpoints-1;
384 
385     /* We have now:
386        i = index of current vertex.
387        prev,next: index of previous/next vertices (of the control polygon)
388 
389        We want:
390         vp, vx, vn: the previous, current and next vertices;
391         start, end: TRUE if we're at an end of poly (then, vp and/or vn are not
392         valid, respectively).
393 
394        Some values *will* be recomputed a few times across iterations (but stored in
395        different boxes). Either gprof says it's a real problem, or gcc finally gets
396        a clue.
397     */
398 
399     if (pts[i].type == BEZ_MOVE_TO) {
400       continue;
401     }
402 
403     switch(pts[i].type) {
404     case BEZ_MOVE_TO:
405       g_assert_not_reached();
406       break;
407     case BEZ_LINE_TO:
408       point_copy(&vx,&pts[i].p1);
409       switch(pts[prev].type) {
410       case BEZ_MOVE_TO:
411       case BEZ_LINE_TO:
412         point_copy(&vsc,&pts[prev].p1);
413         point_copy(&vp,&pts[prev].p1);
414         break;
415       case BEZ_CURVE_TO:
416         point_copy(&vsc,&pts[prev].p3);
417         point_copy(&vp,&pts[prev].p3);
418         break;
419       }
420       break;
421     case BEZ_CURVE_TO:
422       point_copy(&vx,&pts[i].p3);
423       point_copy(&vp,&pts[i].p2);
424       switch(pts[prev].type) {
425       case BEZ_MOVE_TO:
426       case BEZ_LINE_TO:
427         point_copy(&vsc,&pts[prev].p1);
428         break;
429       case BEZ_CURVE_TO:
430         point_copy(&vsc,&pts[prev].p3);
431         break;
432       } /* vsc is the start of the curve. */
433 
434       break;
435     }
436     start = (pts[prev].type == BEZ_MOVE_TO);
437     end = (pts[next].type == BEZ_MOVE_TO);
438     point_copy(&vn,&pts[next].p1); /* whichever type pts[next] is. */
439 
440     /* Now, we know about a few vertices around the one we're dealing with.
441        Depending on the shape of the (previous,current) segment, and whether
442        it's a middle or end segment, we'll be doing different stuff. */
443     if (closed) {
444       if (pts[i].type == BEZ_LINE_TO) {
445         line_bbox(&vsc,&vx,&full_lextra,&rt);
446       } else {
447         bicubicbezier2D_bbox(&vsc,
448                              &pts[i].p1,&pts[i].p2,&pts[i].p3,
449                              &bextra,
450                              &rt);
451       }
452     } else if (start) {
453       if (pts[i].type == BEZ_LINE_TO) {
454         if (end) {
455           line_bbox(&vsc,&vx,&full_lextra,&rt);
456         } else {
457           line_bbox(&vsc,&vx,&start_lextra,&rt);
458         }
459       } else { /* BEZ_MOVE_TO */
460         if (end) {
461           bicubicbezier2D_bbox(&vsc,
462                                &pts[i].p1,&pts[i].p2,&pts[i].p3,
463                                extra,
464                                &rt);
465         } else {
466           bicubicbezier2D_bbox(&vsc,
467                                &pts[i].p1,&pts[i].p2,&pts[i].p3,
468                                &start_bextra,
469                                &rt);
470         }
471       }
472     } else if (end) { /* end but not start. Not closed anyway. */
473       if (pts[i].type == BEZ_LINE_TO) {
474         line_bbox(&vsc,&vx,&end_lextra,&rt);
475       } else {
476         bicubicbezier2D_bbox(&vsc,
477                              &pts[i].p1,&pts[i].p2,&pts[i].p3,
478                              &end_bextra,
479                              &rt);
480       }
481     } else { /* normal case : middle segment (not closed shape). */
482       if (pts[i].type == BEZ_LINE_TO) {
483         line_bbox(&vsc,&vx,&lextra,&rt);
484       } else {
485         bicubicbezier2D_bbox(&vsc,
486                              &pts[i].p1,&pts[i].p2,&pts[i].p3,
487                              &bextra,
488                              &rt);
489       }
490     }
491     rectangle_union(rect,&rt);
492 
493     /* The following code enlarges a little the bounding box (if necessary) to
494        account with the "pointy corners" X (and PS) add when LINEJOIN_MITER mode is
495        in force. */
496 
497     if (!end) { /* only the last segment might not produce overshoot. */
498       Point vpx,vxn;
499       real co,alpha;
500 
501       point_copy_add_scaled(&vpx,&vx,&vp,-1);
502       point_normalize(&vpx);
503       point_copy_add_scaled(&vxn,&vn,&vx,-1);
504       point_normalize(&vxn);
505 
506       co = point_dot(&vpx,&vxn);
507       if (co >= 1.0)
508         alpha = 0.0;
509       else if (co <= -1.0)
510         alpha = M_PI;
511       else
512         alpha = dia_acos(-co);
513       if (co > -0.9816) { /* 0.9816 = cos(11deg) */
514         /* we have a pointy join. */
515         real overshoot;
516         Point vovs,pto;
517 
518 	if (alpha > 0.0 && alpha < M_PI)
519 	  overshoot = extra->middle_trans / sin(alpha/2.0);
520 	else /* prependicular? */
521 	  overshoot = extra->middle_trans;
522 
523         point_copy_add_scaled(&vovs,&vpx,&vxn,-1);
524         point_normalize(&vovs);
525         point_copy_add_scaled(&pto,&vx,&vovs,overshoot);
526 
527         rectangle_add_point(rect,&pto);
528       } else {
529         /* we don't have a pointy join. */
530         Point vpxt,vxnt,tmp;
531 
532         point_get_perp(&vpxt,&vpx);
533         point_get_perp(&vxnt,&vxn);
534 
535         point_copy_add_scaled(&tmp,&vx,&vpxt,1);
536         rectangle_add_point(rect,&tmp);
537         point_copy_add_scaled(&tmp,&vx,&vpxt,-1);
538         rectangle_add_point(rect,&tmp);
539         point_copy_add_scaled(&tmp,&vx,&vxnt,1);
540         rectangle_add_point(rect,&tmp);
541         point_copy_add_scaled(&tmp,&vx,&vxnt,-1);
542         rectangle_add_point(rect,&tmp);
543       }
544     }
545   }
546 }
547 
548 /** Figure out a bounding box for a rectangle (fairly simple:)
549  * @param rin A rectangle to find bbox for.
550  * @param extra Extra information required to find bbox.
551  * @param rout Return value: The enclosing bounding box.
552  * @bug Describe extra info better.
553  */
554 void
rectangle_bbox(const Rectangle * rin,const ElementBBExtras * extra,Rectangle * rout)555 rectangle_bbox(const Rectangle *rin,
556 	       const ElementBBExtras *extra,
557 	       Rectangle *rout)
558 {
559   rout->left = rin->left - extra->border_trans;
560   rout->top = rin->top - extra->border_trans;
561   rout->right = rin->right + extra->border_trans;
562   rout->bottom = rin->bottom + extra->border_trans;
563 }
564 
565 
566 /* TODO: text_bbox ? */
567 
568 
569