1 /**************************************************************************** 2 * 3 * ftbbox.c 4 * 5 * FreeType bbox computation (body). 6 * 7 * Copyright (C) 1996-2023 by 8 * David Turner, Robert Wilhelm, and Werner Lemberg. 9 * 10 * This file is part of the FreeType project, and may only be used 11 * modified and distributed under the terms of the FreeType project 12 * license, LICENSE.TXT. By continuing to use, modify, or distribute 13 * this file you indicate that you have read the license and 14 * understand and accept it fully. 15 * 16 */ 17 18 19 /************************************************************************** 20 * 21 * This component has a _single_ role: to compute exact outline bounding 22 * boxes. 23 * 24 */ 25 26 27 #include <freetype/internal/ftdebug.h> 28 29 #include <freetype/ftbbox.h> 30 #include <freetype/ftimage.h> 31 #include <freetype/ftoutln.h> 32 #include <freetype/internal/ftcalc.h> 33 #include <freetype/internal/ftobjs.h> 34 35 36 typedef struct TBBox_Rec_ 37 { 38 FT_Vector last; 39 FT_BBox bbox; 40 41 } TBBox_Rec; 42 43 44 #define FT_UPDATE_BBOX( p, bbox ) \ 45 FT_BEGIN_STMNT \ 46 if ( p->x < bbox.xMin ) \ 47 bbox.xMin = p->x; \ 48 if ( p->x > bbox.xMax ) \ 49 bbox.xMax = p->x; \ 50 if ( p->y < bbox.yMin ) \ 51 bbox.yMin = p->y; \ 52 if ( p->y > bbox.yMax ) \ 53 bbox.yMax = p->y; \ 54 FT_END_STMNT 55 56 #define CHECK_X( p, bbox ) \ 57 ( p->x < bbox.xMin || p->x > bbox.xMax ) 58 59 #define CHECK_Y( p, bbox ) \ 60 ( p->y < bbox.yMin || p->y > bbox.yMax ) 61 62 63 /************************************************************************** 64 * 65 * @Function: 66 * BBox_Move_To 67 * 68 * @Description: 69 * This function is used as a `move_to' emitter during 70 * FT_Outline_Decompose(). It simply records the destination point 71 * in `user->last'. We also update bbox in case contour starts with 72 * an implicit `on' point. 73 * 74 * @Input: 75 * to :: 76 * A pointer to the destination vector. 77 * 78 * @InOut: 79 * user :: 80 * A pointer to the current walk context. 81 * 82 * @Return: 83 * Always 0. Needed for the interface only. 84 */ 85 FT_CALLBACK_DEF( int ) BBox_Move_To(const FT_Vector * to,void * user_)86 BBox_Move_To( const FT_Vector* to, 87 void* user_ ) 88 { 89 TBBox_Rec* user = (TBBox_Rec*)user_; 90 91 92 FT_UPDATE_BBOX( to, user->bbox ); 93 94 user->last = *to; 95 96 return 0; 97 } 98 99 100 /************************************************************************** 101 * 102 * @Function: 103 * BBox_Line_To 104 * 105 * @Description: 106 * This function is used as a `line_to' emitter during 107 * FT_Outline_Decompose(). It simply records the destination point 108 * in `user->last'; no further computations are necessary because 109 * bbox already contains both explicit ends of the line segment. 110 * 111 * @Input: 112 * to :: 113 * A pointer to the destination vector. 114 * 115 * @InOut: 116 * user :: 117 * A pointer to the current walk context. 118 * 119 * @Return: 120 * Always 0. Needed for the interface only. 121 */ 122 FT_CALLBACK_DEF( int ) BBox_Line_To(const FT_Vector * to,void * user_)123 BBox_Line_To( const FT_Vector* to, 124 void* user_ ) 125 { 126 TBBox_Rec* user = (TBBox_Rec*)user_; 127 128 129 user->last = *to; 130 131 return 0; 132 } 133 134 135 /************************************************************************** 136 * 137 * @Function: 138 * BBox_Conic_Check 139 * 140 * @Description: 141 * Find the extrema of a 1-dimensional conic Bezier curve and update 142 * a bounding range. This version uses direct computation, as it 143 * doesn't need square roots. 144 * 145 * @Input: 146 * y1 :: 147 * The start coordinate. 148 * 149 * y2 :: 150 * The coordinate of the control point. 151 * 152 * y3 :: 153 * The end coordinate. 154 * 155 * @InOut: 156 * min :: 157 * The address of the current minimum. 158 * 159 * max :: 160 * The address of the current maximum. 161 */ 162 static void BBox_Conic_Check(FT_Pos y1,FT_Pos y2,FT_Pos y3,FT_Pos * min,FT_Pos * max)163 BBox_Conic_Check( FT_Pos y1, 164 FT_Pos y2, 165 FT_Pos y3, 166 FT_Pos* min, 167 FT_Pos* max ) 168 { 169 /* This function is only called when a control off-point is outside */ 170 /* the bbox that contains all on-points. It finds a local extremum */ 171 /* within the segment, equal to (y1*y3 - y2*y2)/(y1 - 2*y2 + y3). */ 172 /* Or, offsetting from y2, we get */ 173 174 y1 -= y2; 175 y3 -= y2; 176 y2 += FT_MulDiv( y1, y3, y1 + y3 ); 177 178 if ( y2 < *min ) 179 *min = y2; 180 if ( y2 > *max ) 181 *max = y2; 182 } 183 184 185 /************************************************************************** 186 * 187 * @Function: 188 * BBox_Conic_To 189 * 190 * @Description: 191 * This function is used as a `conic_to' emitter during 192 * FT_Outline_Decompose(). It checks a conic Bezier curve with the 193 * current bounding box, and computes its extrema if necessary to 194 * update it. 195 * 196 * @Input: 197 * control :: 198 * A pointer to a control point. 199 * 200 * to :: 201 * A pointer to the destination vector. 202 * 203 * @InOut: 204 * user :: 205 * The address of the current walk context. 206 * 207 * @Return: 208 * Always 0. Needed for the interface only. 209 * 210 * @Note: 211 * In the case of a non-monotonous arc, we compute directly the 212 * extremum coordinates, as it is sufficiently fast. 213 */ 214 FT_CALLBACK_DEF( int ) BBox_Conic_To(const FT_Vector * control,const FT_Vector * to,void * user_)215 BBox_Conic_To( const FT_Vector* control, 216 const FT_Vector* to, 217 void* user_ ) 218 { 219 TBBox_Rec* user = (TBBox_Rec*)user_; 220 221 222 /* in case `to' is implicit and not included in bbox yet */ 223 FT_UPDATE_BBOX( to, user->bbox ); 224 225 if ( CHECK_X( control, user->bbox ) ) 226 BBox_Conic_Check( user->last.x, 227 control->x, 228 to->x, 229 &user->bbox.xMin, 230 &user->bbox.xMax ); 231 232 if ( CHECK_Y( control, user->bbox ) ) 233 BBox_Conic_Check( user->last.y, 234 control->y, 235 to->y, 236 &user->bbox.yMin, 237 &user->bbox.yMax ); 238 239 user->last = *to; 240 241 return 0; 242 } 243 244 245 /************************************************************************** 246 * 247 * @Function: 248 * BBox_Cubic_Check 249 * 250 * @Description: 251 * Find the extrema of a 1-dimensional cubic Bezier curve and 252 * update a bounding range. This version uses iterative splitting 253 * because it is faster than the exact solution with square roots. 254 * 255 * @Input: 256 * p1 :: 257 * The start coordinate. 258 * 259 * p2 :: 260 * The coordinate of the first control point. 261 * 262 * p3 :: 263 * The coordinate of the second control point. 264 * 265 * p4 :: 266 * The end coordinate. 267 * 268 * @InOut: 269 * min :: 270 * The address of the current minimum. 271 * 272 * max :: 273 * The address of the current maximum. 274 */ 275 static FT_Pos cubic_peak(FT_Pos q1,FT_Pos q2,FT_Pos q3,FT_Pos q4)276 cubic_peak( FT_Pos q1, 277 FT_Pos q2, 278 FT_Pos q3, 279 FT_Pos q4 ) 280 { 281 FT_Pos peak = 0; 282 FT_Int shift; 283 284 285 /* This function finds a peak of a cubic segment if it is above 0 */ 286 /* using iterative bisection of the segment, or returns 0. */ 287 /* The fixed-point arithmetic of bisection is inherently stable */ 288 /* but may loose accuracy in the two lowest bits. To compensate, */ 289 /* we upscale the segment if there is room. Large values may need */ 290 /* to be downscaled to avoid overflows during bisection. */ 291 /* It is called with either q2 or q3 positive, which is necessary */ 292 /* for the peak to exist and avoids undefined FT_MSB. */ 293 294 shift = 27 - FT_MSB( (FT_UInt32)( FT_ABS( q1 ) | 295 FT_ABS( q2 ) | 296 FT_ABS( q3 ) | 297 FT_ABS( q4 ) ) ); 298 299 if ( shift > 0 ) 300 { 301 /* upscaling too much just wastes time */ 302 if ( shift > 2 ) 303 shift = 2; 304 305 q1 *= 1 << shift; 306 q2 *= 1 << shift; 307 q3 *= 1 << shift; 308 q4 *= 1 << shift; 309 } 310 else 311 { 312 q1 >>= -shift; 313 q2 >>= -shift; 314 q3 >>= -shift; 315 q4 >>= -shift; 316 } 317 318 /* for a peak to exist above 0, the cubic segment must have */ 319 /* at least one of its control off-points above 0. */ 320 while ( q2 > 0 || q3 > 0 ) 321 { 322 /* determine which half contains the maximum and split */ 323 if ( q1 + q2 > q3 + q4 ) /* first half */ 324 { 325 q4 = q4 + q3; 326 q3 = q3 + q2; 327 q2 = q2 + q1; 328 q4 = q4 + q3; 329 q3 = q3 + q2; 330 q4 = ( q4 + q3 ) >> 3; 331 q3 = q3 >> 2; 332 q2 = q2 >> 1; 333 } 334 else /* second half */ 335 { 336 q1 = q1 + q2; 337 q2 = q2 + q3; 338 q3 = q3 + q4; 339 q1 = q1 + q2; 340 q2 = q2 + q3; 341 q1 = ( q1 + q2 ) >> 3; 342 q2 = q2 >> 2; 343 q3 = q3 >> 1; 344 } 345 346 /* check whether either end reached the maximum */ 347 if ( q1 == q2 && q1 >= q3 ) 348 { 349 peak = q1; 350 break; 351 } 352 if ( q3 == q4 && q2 <= q4 ) 353 { 354 peak = q4; 355 break; 356 } 357 } 358 359 if ( shift > 0 ) 360 peak >>= shift; 361 else 362 peak <<= -shift; 363 364 return peak; 365 } 366 367 368 static void BBox_Cubic_Check(FT_Pos p1,FT_Pos p2,FT_Pos p3,FT_Pos p4,FT_Pos * min,FT_Pos * max)369 BBox_Cubic_Check( FT_Pos p1, 370 FT_Pos p2, 371 FT_Pos p3, 372 FT_Pos p4, 373 FT_Pos* min, 374 FT_Pos* max ) 375 { 376 /* This function is only called when a control off-point is outside */ 377 /* the bbox that contains all on-points. So at least one of the */ 378 /* conditions below holds and cubic_peak is called with at least one */ 379 /* non-zero argument. */ 380 381 if ( p2 > *max || p3 > *max ) 382 *max += cubic_peak( p1 - *max, p2 - *max, p3 - *max, p4 - *max ); 383 384 /* now flip the signs to update the minimum */ 385 if ( p2 < *min || p3 < *min ) 386 *min -= cubic_peak( *min - p1, *min - p2, *min - p3, *min - p4 ); 387 } 388 389 390 /************************************************************************** 391 * 392 * @Function: 393 * BBox_Cubic_To 394 * 395 * @Description: 396 * This function is used as a `cubic_to' emitter during 397 * FT_Outline_Decompose(). It checks a cubic Bezier curve with the 398 * current bounding box, and computes its extrema if necessary to 399 * update it. 400 * 401 * @Input: 402 * control1 :: 403 * A pointer to the first control point. 404 * 405 * control2 :: 406 * A pointer to the second control point. 407 * 408 * to :: 409 * A pointer to the destination vector. 410 * 411 * @InOut: 412 * user :: 413 * The address of the current walk context. 414 * 415 * @Return: 416 * Always 0. Needed for the interface only. 417 * 418 * @Note: 419 * In the case of a non-monotonous arc, we don't compute directly 420 * extremum coordinates, we subdivide instead. 421 */ 422 FT_CALLBACK_DEF( int ) BBox_Cubic_To(const FT_Vector * control1,const FT_Vector * control2,const FT_Vector * to,void * user_)423 BBox_Cubic_To( const FT_Vector* control1, 424 const FT_Vector* control2, 425 const FT_Vector* to, 426 void* user_ ) 427 { 428 TBBox_Rec* user = (TBBox_Rec*)user_; 429 430 431 /* We don't need to check `to' since it is always an on-point, */ 432 /* thus within the bbox. Only segments with an off-point outside */ 433 /* the bbox can possibly reach new extreme values. */ 434 435 if ( CHECK_X( control1, user->bbox ) || 436 CHECK_X( control2, user->bbox ) ) 437 BBox_Cubic_Check( user->last.x, 438 control1->x, 439 control2->x, 440 to->x, 441 &user->bbox.xMin, 442 &user->bbox.xMax ); 443 444 if ( CHECK_Y( control1, user->bbox ) || 445 CHECK_Y( control2, user->bbox ) ) 446 BBox_Cubic_Check( user->last.y, 447 control1->y, 448 control2->y, 449 to->y, 450 &user->bbox.yMin, 451 &user->bbox.yMax ); 452 453 user->last = *to; 454 455 return 0; 456 } 457 458 459 FT_DEFINE_OUTLINE_FUNCS( 460 bbox_interface, 461 462 (FT_Outline_MoveTo_Func) BBox_Move_To, /* move_to */ 463 (FT_Outline_LineTo_Func) BBox_Line_To, /* line_to */ 464 (FT_Outline_ConicTo_Func)BBox_Conic_To, /* conic_to */ 465 (FT_Outline_CubicTo_Func)BBox_Cubic_To, /* cubic_to */ 466 0, /* shift */ 467 0 /* delta */ 468 ) 469 470 471 /* documentation is in ftbbox.h */ 472 FT_EXPORT_DEF(FT_Error)473 FT_EXPORT_DEF( FT_Error ) 474 FT_Outline_Get_BBox( FT_Outline* outline, 475 FT_BBox *abbox ) 476 { 477 FT_BBox cbox = { 0x7FFFFFFFL, 0x7FFFFFFFL, 478 -0x7FFFFFFFL, -0x7FFFFFFFL }; 479 FT_BBox bbox = { 0x7FFFFFFFL, 0x7FFFFFFFL, 480 -0x7FFFFFFFL, -0x7FFFFFFFL }; 481 FT_Vector* vec; 482 FT_UShort n; 483 484 485 if ( !abbox ) 486 return FT_THROW( Invalid_Argument ); 487 488 if ( !outline ) 489 return FT_THROW( Invalid_Outline ); 490 491 /* if outline is empty, return (0,0,0,0) */ 492 if ( outline->n_points == 0 || outline->n_contours <= 0 ) 493 { 494 abbox->xMin = abbox->xMax = 0; 495 abbox->yMin = abbox->yMax = 0; 496 497 return 0; 498 } 499 500 /* We compute the control box as well as the bounding box of */ 501 /* all `on' points in the outline. Then, if the two boxes */ 502 /* coincide, we exit immediately. */ 503 504 vec = outline->points; 505 506 for ( n = 0; n < outline->n_points; n++ ) 507 { 508 FT_UPDATE_BBOX( vec, cbox ); 509 510 if ( FT_CURVE_TAG( outline->tags[n] ) == FT_CURVE_TAG_ON ) 511 FT_UPDATE_BBOX( vec, bbox ); 512 513 vec++; 514 } 515 516 /* test two boxes for equality */ 517 if ( cbox.xMin < bbox.xMin || cbox.xMax > bbox.xMax || 518 cbox.yMin < bbox.yMin || cbox.yMax > bbox.yMax ) 519 { 520 /* the two boxes are different, now walk over the outline to */ 521 /* get the Bezier arc extrema. */ 522 523 FT_Error error; 524 TBBox_Rec user; 525 526 527 user.bbox = bbox; 528 529 error = FT_Outline_Decompose( outline, &bbox_interface, &user ); 530 if ( error ) 531 return error; 532 533 *abbox = user.bbox; 534 } 535 else 536 *abbox = bbox; 537 538 return FT_Err_Ok; 539 } 540 541 542 /* END */ 543