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