misc fixes to get rrdtool working without included libraries.
[rrdtool.git] / libraries / freetype-2.0.5 / ahoptim.c
1 /***************************************************************************/
2 /*                                                                         */
3 /*  ahoptim.c                                                              */
4 /*                                                                         */
5 /*    FreeType auto hinting outline optimization (body).                   */
6 /*                                                                         */
7 /*  Copyright 2000-2001 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 ALLOC_ARRAY() and FREE() */
36 #include "ahoptim.h"
37
38
39   /* define this macro to use brute force optimisation -- 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 #include <string.h>
65
66 #define FLOAT( x )  ( (float)( (x) / 64.0 ) )
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 ( ALLOC_ARRAY( stems, num_stems, AH_Stem ) )
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 ( ALLOC_ARRAY( springs, num_springs, AH_Spring ) )
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       FREE( optimizer->horz_stems );
800       FREE( optimizer->vert_stems );
801       FREE( optimizer->horz_springs );
802       FREE( optimizer->vert_springs );
803       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     MEM_Set( optimizer, 0, 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 ( ALLOC_ARRAY( optimizer->positions,
838                         max_stems * AH_MAX_CONFIGS, FT_Pos ) )
839         goto Fail;
840
841       optimizer->num_configs = 0;
842       for ( n = 0; n < AH_MAX_CONFIGS; n++ )
843         optimizer->configs[n].positions = optimizer->positions +
844                                           n * max_stems;
845     }
846
847     return error;
848
849   Fail:
850     AH_Optimizer_Done( optimizer );
851     return error;
852   }
853
854
855   /* compute optimal outline */
856   void
857   AH_Optimizer_Compute( AH_Optimizer*  optimizer )
858   {
859     optimizer->num_stems   = optimizer->num_hstems;
860     optimizer->stems       = optimizer->horz_stems;
861     optimizer->num_springs = optimizer->num_hsprings;
862     optimizer->springs     = optimizer->horz_springs;
863
864     if ( optimizer->num_springs > 0 )
865     {
866       LOG(( "horizontal optimization ------------------------\n" ));
867       optim_compute( optimizer );
868     }
869
870     optimizer->num_stems   = optimizer->num_vstems;
871     optimizer->stems       = optimizer->vert_stems;
872     optimizer->num_springs = optimizer->num_vsprings;
873     optimizer->springs     = optimizer->vert_springs;
874
875     if ( optimizer->num_springs )
876     {
877       LOG(( "vertical optimization --------------------------\n" ));
878       optim_compute( optimizer );
879     }
880   }
881
882
883 /* END */