]> git.saurik.com Git - wxWidgets.git/blame - src/freetype/autohint/ahoptim.c
corrected dynamic class implementation
[wxWidgets.git] / src / freetype / autohint / ahoptim.c
CommitLineData
cabec872
RR
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 <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 );
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 */