1 /***************************************************************************/ 2 /* */ 3 /* ahoptim.c */ 4 /* */ 5 /* FreeType auto hinting outline optimization (body). */ 6 /* */ 7 /* Copyright 2000-2001, 2002 Catharon Productions Inc. */ 8 /* Author: David Turner */ 9 /* */ 10 /* This file is part of the Catharon Typography Project and shall only */ 11 /* be used, modified, and distributed under the terms of the Catharon */ 12 /* Open Source License that should come with this file under the name */ 13 /* `CatharonLicense.txt'. By continuing to use, modify, or distribute */ 14 /* this file you indicate that you have read the license and */ 15 /* understand and accept it fully. */ 16 /* */ 17 /* Note that this license is compatible with the FreeType license. */ 18 /* */ 19 /***************************************************************************/ 20 21 22 /*************************************************************************/ 23 /* */ 24 /* This module is in charge of optimising the outlines produced by the */ 25 /* auto-hinter in direct mode. This is required at small pixel sizes in */ 26 /* order to ensure coherent spacing, among other things.. */ 27 /* */ 28 /* The technique used in this module is a simplified simulated */ 29 /* annealing. */ 30 /* */ 31 /*************************************************************************/ 32 33 34 #include <ft2build.h> 35 #include FT_INTERNAL_OBJECTS_H /* for FT_ALLOC_ARRAY() and FT_FREE() */ 36 #include "ahoptim.h" 37 38 39 /* define this macro to use brute force optimization -- this is slow, */ 40 /* but a good way to perfect the distortion function `by hand' through */ 41 /* tweaking */ 42 #define AH_BRUTE_FORCE 43 44 45 #define xxxAH_DEBUG_OPTIM 46 47 48 #undef LOG 49 #ifdef AH_DEBUG_OPTIM 50 51 #define LOG( x ) optim_log ## x 52 53 #else 54 55 #define LOG( x ) 56 57 #endif /* AH_DEBUG_OPTIM */ 58 59 60 #ifdef AH_DEBUG_OPTIM 61 62 #include <stdarg.h> 63 #include <stdlib.h> 64 65 #define FLOAT( x ) ( (float)( (x) / 64.0 ) ) 66 67 68 static void 69 optim_log( const char* fmt, ... ) 70 { 71 va_list ap; 72 73 74 va_start( ap, fmt ); 75 vprintf( fmt, ap ); 76 va_end( ap ); 77 } 78 79 80 static void 81 AH_Dump_Stems( AH_Optimizer* optimizer ) 82 { 83 int n; 84 AH_Stem* stem; 85 86 87 stem = optimizer->stems; 88 for ( n = 0; n < optimizer->num_stems; n++, stem++ ) 89 { 90 LOG(( " %c%2d [%.1f:%.1f]={%.1f:%.1f}=" 91 "<%1.f..%1.f> force=%.1f speed=%.1f\n", 92 optimizer->vertical ? 'V' : 'H', n, 93 FLOAT( stem->edge1->opos ), FLOAT( stem->edge2->opos ), 94 FLOAT( stem->edge1->pos ), FLOAT( stem->edge2->pos ), 95 FLOAT( stem->min_pos ), FLOAT( stem->max_pos ), 96 FLOAT( stem->force ), FLOAT( stem->velocity ) )); 97 } 98 } 99 100 101 static void 102 AH_Dump_Stems2( AH_Optimizer* optimizer ) 103 { 104 int n; 105 AH_Stem* stem; 106 107 108 stem = optimizer->stems; 109 for ( n = 0; n < optimizer->num_stems; n++, stem++ ) 110 { 111 LOG(( " %c%2d [%.1f]=<%1.f..%1.f> force=%.1f speed=%.1f\n", 112 optimizer->vertical ? 'V' : 'H', n, 113 FLOAT( stem->pos ), 114 FLOAT( stem->min_pos ), FLOAT( stem->max_pos ), 115 FLOAT( stem->force ), FLOAT( stem->velocity ) )); 116 } 117 } 118 119 120 static void 121 AH_Dump_Springs( AH_Optimizer* optimizer ) 122 { 123 int n; 124 AH_Spring* spring; 125 AH_Stem* stems; 126 127 128 spring = optimizer->springs; 129 stems = optimizer->stems; 130 LOG(( "%cSprings ", optimizer->vertical ? 'V' : 'H' )); 131 132 for ( n = 0; n < optimizer->num_springs; n++, spring++ ) 133 { 134 LOG(( " [%d-%d:%.1f:%1.f:%.1f]", 135 spring->stem1 - stems, spring->stem2 - stems, 136 FLOAT( spring->owidth ), 137 FLOAT( spring->stem2->pos - 138 ( spring->stem1->pos + spring->stem1->width ) ), 139 FLOAT( spring->tension ) )); 140 } 141 142 LOG(( "\n" )); 143 } 144 145 #endif /* AH_DEBUG_OPTIM */ 146 147 148 /*************************************************************************/ 149 /*************************************************************************/ 150 /*************************************************************************/ 151 /**** ****/ 152 /**** COMPUTE STEMS AND SPRINGS IN AN OUTLINE ****/ 153 /**** ****/ 154 /*************************************************************************/ 155 /*************************************************************************/ 156 /*************************************************************************/ 157 158 159 static int 160 valid_stem_segments( AH_Segment seg1, 161 AH_Segment seg2 ) 162 { 163 return seg1->serif == 0 && 164 seg2 && 165 seg2->link == seg1 && 166 seg1->pos < seg2->pos && 167 seg1->min_coord <= seg2->max_coord && 168 seg2->min_coord <= seg1->max_coord; 169 } 170 171 172 /* compute all stems in an outline */ 173 static int 174 optim_compute_stems( AH_Optimizer* optimizer ) 175 { 176 AH_Outline outline = optimizer->outline; 177 FT_Fixed scale; 178 FT_Memory memory = optimizer->memory; 179 FT_Error error = 0; 180 FT_Int dimension; 181 AH_Edge edges; 182 AH_Edge edge_limit; 183 AH_Stem** p_stems; 184 FT_Int* p_num_stems; 185 186 187 edges = outline->horz_edges; 188 edge_limit = edges + outline->num_hedges; 189 scale = outline->y_scale; 190 191 p_stems = &optimizer->horz_stems; 192 p_num_stems = &optimizer->num_hstems; 193 194 for ( dimension = 1; dimension >= 0; dimension-- ) 195 { 196 AH_Stem* stems = 0; 197 FT_Int num_stems = 0; 198 AH_Edge edge; 199 200 201 /* first of all, count the number of stems in this direction */ 202 for ( edge = edges; edge < edge_limit; edge++ ) 203 { 204 AH_Segment seg = edge->first; 205 206 207 do 208 { 209 if (valid_stem_segments( seg, seg->link ) ) 210 num_stems++; 211 212 seg = seg->edge_next; 213 214 } while ( seg != edge->first ); 215 } 216 217 /* now allocate the stems and build their table */ 218 if ( num_stems > 0 ) 219 { 220 AH_Stem* stem; 221 222 223 if ( FT_NEW_ARRAY( stems, num_stems ) ) 224 goto Exit; 225 226 stem = stems; 227 for ( edge = edges; edge < edge_limit; edge++ ) 228 { 229 AH_Segment seg = edge->first; 230 AH_Segment seg2; 231 232 233 do 234 { 235 seg2 = seg->link; 236 if ( valid_stem_segments( seg, seg2 ) ) 237 { 238 AH_Edge edge1 = seg->edge; 239 AH_Edge edge2 = seg2->edge; 240 241 242 stem->edge1 = edge1; 243 stem->edge2 = edge2; 244 stem->opos = edge1->opos; 245 stem->pos = edge1->pos; 246 stem->owidth = edge2->opos - edge1->opos; 247 stem->width = edge2->pos - edge1->pos; 248 249 /* compute min_coord and max_coord */ 250 { 251 FT_Pos min_coord = seg->min_coord; 252 FT_Pos max_coord = seg->max_coord; 253 254 255 if ( seg2->min_coord > min_coord ) 256 min_coord = seg2->min_coord; 257 258 if ( seg2->max_coord < max_coord ) 259 max_coord = seg2->max_coord; 260 261 stem->min_coord = min_coord; 262 stem->max_coord = max_coord; 263 } 264 265 /* compute minimum and maximum positions for stem -- */ 266 /* note that the left-most/bottom-most stem has always */ 267 /* a fixed position */ 268 if ( stem == stems || edge1->blue_edge || edge2->blue_edge ) 269 { 270 /* this stem cannot move; it is snapped to a blue edge */ 271 stem->min_pos = stem->pos; 272 stem->max_pos = stem->pos; 273 } 274 else 275 { 276 /* this edge can move; compute its min and max positions */ 277 FT_Pos pos1 = stem->opos; 278 FT_Pos pos2 = pos1 + stem->owidth - stem->width; 279 FT_Pos min1 = pos1 & -64; 280 FT_Pos min2 = pos2 & -64; 281 282 283 stem->min_pos = min1; 284 stem->max_pos = min1 + 64; 285 if ( min2 < min1 ) 286 stem->min_pos = min2; 287 else 288 stem->max_pos = min2 + 64; 289 290 /* XXX: just to see what it does */ 291 stem->max_pos += 64; 292 293 /* just for the case where direct hinting did some */ 294 /* incredible things (e.g. blue edge shifts) */ 295 if ( stem->min_pos > stem->pos ) 296 stem->min_pos = stem->pos; 297 298 if ( stem->max_pos < stem->pos ) 299 stem->max_pos = stem->pos; 300 } 301 302 stem->velocity = 0; 303 stem->force = 0; 304 305 stem++; 306 } 307 seg = seg->edge_next; 308 309 } while ( seg != edge->first ); 310 } 311 } 312 313 *p_stems = stems; 314 *p_num_stems = num_stems; 315 316 edges = outline->vert_edges; 317 edge_limit = edges + outline->num_vedges; 318 scale = outline->x_scale; 319 320 p_stems = &optimizer->vert_stems; 321 p_num_stems = &optimizer->num_vstems; 322 } 323 324 Exit: 325 326 #ifdef AH_DEBUG_OPTIM 327 AH_Dump_Stems( optimizer ); 328 #endif 329 330 return error; 331 } 332 333 334 /* returns the spring area between two stems, 0 if none */ 335 static FT_Pos 336 stem_spring_area( AH_Stem* stem1, 337 AH_Stem* stem2 ) 338 { 339 FT_Pos area1 = stem1->max_coord - stem1->min_coord; 340 FT_Pos area2 = stem2->max_coord - stem2->min_coord; 341 FT_Pos min = stem1->min_coord; 342 FT_Pos max = stem1->max_coord; 343 FT_Pos area; 344 345 346 /* order stems */ 347 if ( stem2->opos <= stem1->opos + stem1->owidth ) 348 return 0; 349 350 if ( min < stem2->min_coord ) 351 min = stem2->min_coord; 352 353 if ( max < stem2->max_coord ) 354 max = stem2->max_coord; 355 356 area = ( max-min ); 357 if ( 2 * area < area1 && 2 * area < area2 ) 358 area = 0; 359 360 return area; 361 } 362 363 364 /* compute all springs in an outline */ 365 static int 366 optim_compute_springs( AH_Optimizer* optimizer ) 367 { 368 /* basically, a spring exists between two stems if most of their */ 369 /* surface is aligned */ 370 FT_Memory memory = optimizer->memory; 371 372 AH_Stem* stems; 373 AH_Stem* stem_limit; 374 AH_Stem* stem; 375 int dimension; 376 int error = 0; 377 378 FT_Int* p_num_springs; 379 AH_Spring** p_springs; 380 381 382 stems = optimizer->horz_stems; 383 stem_limit = stems + optimizer->num_hstems; 384 385 p_springs = &optimizer->horz_springs; 386 p_num_springs = &optimizer->num_hsprings; 387 388 for ( dimension = 1; dimension >= 0; dimension-- ) 389 { 390 FT_Int num_springs = 0; 391 AH_Spring* springs = 0; 392 393 394 /* first of all, count stem springs */ 395 for ( stem = stems; stem + 1 < stem_limit; stem++ ) 396 { 397 AH_Stem* stem2; 398 399 400 for ( stem2 = stem+1; stem2 < stem_limit; stem2++ ) 401 if ( stem_spring_area( stem, stem2 ) ) 402 num_springs++; 403 } 404 405 /* then allocate and build the springs table */ 406 if ( num_springs > 0 ) 407 { 408 AH_Spring* spring; 409 410 411 /* allocate table of springs */ 412 if ( FT_NEW_ARRAY( springs, num_springs ) ) 413 goto Exit; 414 415 /* fill the springs table */ 416 spring = springs; 417 for ( stem = stems; stem+1 < stem_limit; stem++ ) 418 { 419 AH_Stem* stem2; 420 FT_Pos area; 421 422 423 for ( stem2 = stem + 1; stem2 < stem_limit; stem2++ ) 424 { 425 area = stem_spring_area( stem, stem2 ); 426 if ( area ) 427 { 428 /* add a new spring here */ 429 spring->stem1 = stem; 430 spring->stem2 = stem2; 431 spring->owidth = stem2->opos - ( stem->opos + stem->owidth ); 432 spring->tension = 0; 433 434 spring++; 435 } 436 } 437 } 438 } 439 *p_num_springs = num_springs; 440 *p_springs = springs; 441 442 stems = optimizer->vert_stems; 443 stem_limit = stems + optimizer->num_vstems; 444 445 p_springs = &optimizer->vert_springs; 446 p_num_springs = &optimizer->num_vsprings; 447 } 448 449 Exit: 450 451 #ifdef AH_DEBUG_OPTIM 452 AH_Dump_Springs( optimizer ); 453 #endif 454 455 return error; 456 } 457 458 459 /*************************************************************************/ 460 /*************************************************************************/ 461 /*************************************************************************/ 462 /**** ****/ 463 /**** OPTIMIZE THROUGH MY STRANGE SIMULATED ANNEALING ALGO ;-) ****/ 464 /**** ****/ 465 /*************************************************************************/ 466 /*************************************************************************/ 467 /*************************************************************************/ 468 469 #ifndef AH_BRUTE_FORCE 470 471 /* compute all spring tensions */ 472 static void 473 optim_compute_tensions( AH_Optimizer* optimizer ) 474 { 475 AH_Spring* spring = optimizer->springs; 476 AH_Spring* limit = spring + optimizer->num_springs; 477 478 479 for ( ; spring < limit; spring++ ) 480 { 481 AH_Stem* stem1 = spring->stem1; 482 AH_Stem* stem2 = spring->stem2; 483 FT_Int status; 484 485 FT_Pos width; 486 FT_Pos tension; 487 FT_Pos sign; 488 489 490 /* compute the tension; it simply is -K*(new_width-old_width) */ 491 width = stem2->pos - ( stem1->pos + stem1->width ); 492 tension = width - spring->owidth; 493 494 sign = 1; 495 if ( tension < 0 ) 496 { 497 sign = -1; 498 tension = -tension; 499 } 500 501 if ( width <= 0 ) 502 tension = 32000; 503 else 504 tension = ( tension << 10 ) / width; 505 506 tension = -sign * FT_MulFix( tension, optimizer->tension_scale ); 507 spring->tension = tension; 508 509 /* now, distribute tension among the englobing stems, if they */ 510 /* are able to move */ 511 status = 0; 512 if ( stem1->pos <= stem1->min_pos ) 513 status |= 1; 514 if ( stem2->pos >= stem2->max_pos ) 515 status |= 2; 516 517 if ( !status ) 518 tension /= 2; 519 520 if ( ( status & 1 ) == 0 ) 521 stem1->force -= tension; 522 523 if ( ( status & 2 ) == 0 ) 524 stem2->force += tension; 525 } 526 } 527 528 529 /* compute all stem movements -- returns 0 if nothing moved */ 530 static int 531 optim_compute_stem_movements( AH_Optimizer* optimizer ) 532 { 533 AH_Stem* stems = optimizer->stems; 534 AH_Stem* limit = stems + optimizer->num_stems; 535 AH_Stem* stem = stems; 536 int moved = 0; 537 538 539 /* set initial forces to velocity */ 540 for ( stem = stems; stem < limit; stem++ ) 541 { 542 stem->force = stem->velocity; 543 stem->velocity /= 2; /* XXX: Heuristics */ 544 } 545 546 /* compute the sum of forces applied on each stem */ 547 optim_compute_tensions( optimizer ); 548 549 #ifdef AH_DEBUG_OPTIM 550 AH_Dump_Springs( optimizer ); 551 AH_Dump_Stems2( optimizer ); 552 #endif 553 554 /* now, see whether something can move */ 555 for ( stem = stems; stem < limit; stem++ ) 556 { 557 if ( stem->force > optimizer->tension_threshold ) 558 { 559 /* there is enough tension to move the stem to the right */ 560 if ( stem->pos < stem->max_pos ) 561 { 562 stem->pos += 64; 563 stem->velocity = stem->force / 2; 564 moved = 1; 565 } 566 else 567 stem->velocity = 0; 568 } 569 else if ( stem->force < optimizer->tension_threshold ) 570 { 571 /* there is enough tension to move the stem to the left */ 572 if ( stem->pos > stem->min_pos ) 573 { 574 stem->pos -= 64; 575 stem->velocity = stem->force / 2; 576 moved = 1; 577 } 578 else 579 stem->velocity = 0; 580 } 581 } 582 583 /* return 0 if nothing moved */ 584 return moved; 585 } 586 587 #endif /* AH_BRUTE_FORCE */ 588 589 590 /* compute current global distortion from springs */ 591 static FT_Pos 592 optim_compute_distortion( AH_Optimizer* optimizer ) 593 { 594 AH_Spring* spring = optimizer->springs; 595 AH_Spring* limit = spring + optimizer->num_springs; 596 FT_Pos distortion = 0; 597 598 599 for ( ; spring < limit; spring++ ) 600 { 601 AH_Stem* stem1 = spring->stem1; 602 AH_Stem* stem2 = spring->stem2; 603 FT_Pos width; 604 605 width = stem2->pos - ( stem1->pos + stem1->width ); 606 width -= spring->owidth; 607 if ( width < 0 ) 608 width = -width; 609 610 distortion += width; 611 } 612 613 return distortion; 614 } 615 616 617 /* record stems configuration in `best of' history */ 618 static void 619 optim_record_configuration( AH_Optimizer* optimizer ) 620 { 621 FT_Pos distortion; 622 AH_Configuration* configs = optimizer->configs; 623 AH_Configuration* limit = configs + optimizer->num_configs; 624 AH_Configuration* config; 625 626 627 distortion = optim_compute_distortion( optimizer ); 628 LOG(( "config distortion = %.1f ", FLOAT( distortion * 64 ) )); 629 630 /* check that we really need to add this configuration to our */ 631 /* sorted history */ 632 if ( limit > configs && limit[-1].distortion < distortion ) 633 { 634 LOG(( "ejected\n" )); 635 return; 636 } 637 638 /* add new configuration at the end of the table */ 639 { 640 int n; 641 642 643 config = limit; 644 if ( optimizer->num_configs < AH_MAX_CONFIGS ) 645 optimizer->num_configs++; 646 else 647 config--; 648 649 config->distortion = distortion; 650 651 for ( n = 0; n < optimizer->num_stems; n++ ) 652 config->positions[n] = optimizer->stems[n].pos; 653 } 654 655 /* move the current configuration towards the front of the list */ 656 /* when necessary -- yes this is slow bubble sort ;-) */ 657 while ( config > configs && config[0].distortion < config[-1].distortion ) 658 { 659 AH_Configuration temp; 660 661 662 config--; 663 temp = config[0]; 664 config[0] = config[1]; 665 config[1] = temp; 666 } 667 LOG(( "recorded!\n" )); 668 } 669 670 671 #ifdef AH_BRUTE_FORCE 672 673 /* optimize outline in a single direction */ 674 static void 675 optim_compute( AH_Optimizer* optimizer ) 676 { 677 int n; 678 FT_Bool moved; 679 680 AH_Stem* stem = optimizer->stems; 681 AH_Stem* limit = stem + optimizer->num_stems; 682 683 684 /* empty, exit */ 685 if ( stem >= limit ) 686 return; 687 688 optimizer->num_configs = 0; 689 690 stem = optimizer->stems; 691 for ( ; stem < limit; stem++ ) 692 stem->pos = stem->min_pos; 693 694 do 695 { 696 /* record current configuration */ 697 optim_record_configuration( optimizer ); 698 699 /* now change configuration */ 700 moved = 0; 701 for ( stem = optimizer->stems; stem < limit; stem++ ) 702 { 703 if ( stem->pos < stem->max_pos ) 704 { 705 stem->pos += 64; 706 moved = 1; 707 break; 708 } 709 710 stem->pos = stem->min_pos; 711 } 712 } while ( moved ); 713 714 /* now, set the best stem positions */ 715 for ( n = 0; n < optimizer->num_stems; n++ ) 716 { 717 AH_Stem* stem = optimizer->stems + n; 718 FT_Pos pos = optimizer->configs[0].positions[n]; 719 720 721 stem->edge1->pos = pos; 722 stem->edge2->pos = pos + stem->width; 723 724 stem->edge1->flags |= AH_EDGE_DONE; 725 stem->edge2->flags |= AH_EDGE_DONE; 726 } 727 } 728 729 #else /* AH_BRUTE_FORCE */ 730 731 /* optimize outline in a single direction */ 732 static void 733 optim_compute( AH_Optimizer* optimizer ) 734 { 735 int n, counter, counter2; 736 737 738 optimizer->num_configs = 0; 739 optimizer->tension_scale = 0x80000L; 740 optimizer->tension_threshold = 64; 741 742 /* record initial configuration threshold */ 743 optim_record_configuration( optimizer ); 744 745 counter = 0; 746 for ( counter2 = optimizer->num_stems*8; counter2 >= 0; counter2-- ) 747 { 748 if ( counter == 0 ) 749 counter = 2 * optimizer->num_stems; 750 751 if ( !optim_compute_stem_movements( optimizer ) ) 752 break; 753 754 optim_record_configuration( optimizer ); 755 756 counter--; 757 if ( counter == 0 ) 758 optimizer->tension_scale /= 2; 759 } 760 761 /* now, set the best stem positions */ 762 for ( n = 0; n < optimizer->num_stems; n++ ) 763 { 764 AH_Stem* stem = optimizer->stems + n; 765 FT_Pos pos = optimizer->configs[0].positions[n]; 766 767 768 stem->edge1->pos = pos; 769 stem->edge2->pos = pos + stem->width; 770 771 stem->edge1->flags |= AH_EDGE_DONE; 772 stem->edge2->flags |= AH_EDGE_DONE; 773 } 774 } 775 776 #endif /* AH_BRUTE_FORCE */ 777 778 779 /*************************************************************************/ 780 /*************************************************************************/ 781 /*************************************************************************/ 782 /**** ****/ 783 /**** HIGH-LEVEL OPTIMIZER API ****/ 784 /**** ****/ 785 /*************************************************************************/ 786 /*************************************************************************/ 787 /*************************************************************************/ 788 789 790 /* releases the optimization data */ 791 void 792 AH_Optimizer_Done( AH_Optimizer* optimizer ) 793 { 794 if ( optimizer ) 795 { 796 FT_Memory memory = optimizer->memory; 797 798 799 FT_FREE( optimizer->horz_stems ); 800 FT_FREE( optimizer->vert_stems ); 801 FT_FREE( optimizer->horz_springs ); 802 FT_FREE( optimizer->vert_springs ); 803 FT_FREE( optimizer->positions ); 804 } 805 } 806 807 808 /* loads the outline into the optimizer */ 809 int 810 AH_Optimizer_Init( AH_Optimizer* optimizer, 811 AH_Outline outline, 812 FT_Memory memory ) 813 { 814 FT_Error error; 815 816 817 FT_MEM_ZERO( optimizer, sizeof ( *optimizer ) ); 818 optimizer->outline = outline; 819 optimizer->memory = memory; 820 821 LOG(( "initializing new optimizer\n" )); 822 /* compute stems and springs */ 823 error = optim_compute_stems ( optimizer ) || 824 optim_compute_springs( optimizer ); 825 if ( error ) 826 goto Fail; 827 828 /* allocate stem positions history and configurations */ 829 { 830 int n, max_stems; 831 832 833 max_stems = optimizer->num_hstems; 834 if ( max_stems < optimizer->num_vstems ) 835 max_stems = optimizer->num_vstems; 836 837 if ( FT_NEW_ARRAY( optimizer->positions, max_stems * AH_MAX_CONFIGS ) ) 838 goto Fail; 839 840 optimizer->num_configs = 0; 841 for ( n = 0; n < AH_MAX_CONFIGS; n++ ) 842 optimizer->configs[n].positions = optimizer->positions + 843 n * max_stems; 844 } 845 846 return error; 847 848 Fail: 849 AH_Optimizer_Done( optimizer ); 850 return error; 851 } 852 853 854 /* compute optimal outline */ 855 void 856 AH_Optimizer_Compute( AH_Optimizer* optimizer ) 857 { 858 optimizer->num_stems = optimizer->num_hstems; 859 optimizer->stems = optimizer->horz_stems; 860 optimizer->num_springs = optimizer->num_hsprings; 861 optimizer->springs = optimizer->horz_springs; 862 863 if ( optimizer->num_springs > 0 ) 864 { 865 LOG(( "horizontal optimization ------------------------\n" )); 866 optim_compute( optimizer ); 867 } 868 869 optimizer->num_stems = optimizer->num_vstems; 870 optimizer->stems = optimizer->vert_stems; 871 optimizer->num_springs = optimizer->num_vsprings; 872 optimizer->springs = optimizer->vert_springs; 873 874 if ( optimizer->num_springs ) 875 { 876 LOG(( "vertical optimization --------------------------\n" )); 877 optim_compute( optimizer ); 878 } 879 } 880 881 882 /* END */ 883