Bringing apdf from vendor into main branch.
[AROS-Contrib.git] / apdf / freetype2 / autohint / ahoptim.c
blob6b4cdf0c6cb27246fb556b82b0648336474df46c
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 /***************************************************************************/
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 /*************************************************************************/
34 #include <ft2build.h>
35 #include FT_INTERNAL_OBJECTS_H /* for FT_ALLOC_ARRAY() and FT_FREE() */
36 #include "ahoptim.h"
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
45 #define xxxAH_DEBUG_OPTIM
48 #undef LOG
49 #ifdef AH_DEBUG_OPTIM
51 #define LOG( x ) optim_log ## x
53 #else
55 #define LOG( x )
57 #endif /* AH_DEBUG_OPTIM */
60 #ifdef AH_DEBUG_OPTIM
62 #include <stdarg.h>
63 #include <stdlib.h>
65 #define FLOAT( x ) ( (float)( (x) / 64.0 ) )
68 static void
69 optim_log( const char* fmt, ... )
71 va_list ap;
74 va_start( ap, fmt );
75 vprintf( fmt, ap );
76 va_end( ap );
80 static void
81 AH_Dump_Stems( AH_Optimizer* optimizer )
83 int n;
84 AH_Stem* stem;
87 stem = optimizer->stems;
88 for ( n = 0; n < optimizer->num_stems; n++, stem++ )
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 ) ));
101 static void
102 AH_Dump_Stems2( AH_Optimizer* optimizer )
104 int n;
105 AH_Stem* stem;
108 stem = optimizer->stems;
109 for ( n = 0; n < optimizer->num_stems; n++, stem++ )
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 ) ));
120 static void
121 AH_Dump_Springs( AH_Optimizer* optimizer )
123 int n;
124 AH_Spring* spring;
125 AH_Stem* stems;
128 spring = optimizer->springs;
129 stems = optimizer->stems;
130 LOG(( "%cSprings ", optimizer->vertical ? 'V' : 'H' ));
132 for ( n = 0; n < optimizer->num_springs; n++, spring++ )
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 ) ));
142 LOG(( "\n" ));
145 #endif /* AH_DEBUG_OPTIM */
148 /*************************************************************************/
149 /*************************************************************************/
150 /*************************************************************************/
151 /**** ****/
152 /**** COMPUTE STEMS AND SPRINGS IN AN OUTLINE ****/
153 /**** ****/
154 /*************************************************************************/
155 /*************************************************************************/
156 /*************************************************************************/
159 static int
160 valid_stem_segments( AH_Segment* seg1,
161 AH_Segment* seg2 )
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;
172 /* compute all stems in an outline */
173 static int
174 optim_compute_stems( AH_Optimizer* optimizer )
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;
187 edges = outline->horz_edges;
188 edge_limit = edges + outline->num_hedges;
189 scale = outline->y_scale;
191 p_stems = &optimizer->horz_stems;
192 p_num_stems = &optimizer->num_hstems;
194 for ( dimension = 1; dimension >= 0; dimension-- )
196 AH_Stem* stems = 0;
197 FT_Int num_stems = 0;
198 AH_Edge* edge;
201 /* first of all, count the number of stems in this direction */
202 for ( edge = edges; edge < edge_limit; edge++ )
204 AH_Segment* seg = edge->first;
209 if (valid_stem_segments( seg, seg->link ) )
210 num_stems++;
212 seg = seg->edge_next;
214 } while ( seg != edge->first );
217 /* now allocate the stems and build their table */
218 if ( num_stems > 0 )
220 AH_Stem* stem;
223 if ( FT_NEW_ARRAY( stems, num_stems ) )
224 goto Exit;
226 stem = stems;
227 for ( edge = edges; edge < edge_limit; edge++ )
229 AH_Segment* seg = edge->first;
230 AH_Segment* seg2;
235 seg2 = seg->link;
236 if ( valid_stem_segments( seg, seg2 ) )
238 AH_Edge* edge1 = seg->edge;
239 AH_Edge* edge2 = seg2->edge;
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;
249 /* compute min_coord and max_coord */
251 FT_Pos min_coord = seg->min_coord;
252 FT_Pos max_coord = seg->max_coord;
255 if ( seg2->min_coord > min_coord )
256 min_coord = seg2->min_coord;
258 if ( seg2->max_coord < max_coord )
259 max_coord = seg2->max_coord;
261 stem->min_coord = min_coord;
262 stem->max_coord = max_coord;
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 )
270 /* this stem cannot move; it is snapped to a blue edge */
271 stem->min_pos = stem->pos;
272 stem->max_pos = stem->pos;
274 else
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;
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;
290 /* XXX: just to see what it does */
291 stem->max_pos += 64;
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;
298 if ( stem->max_pos < stem->pos )
299 stem->max_pos = stem->pos;
302 stem->velocity = 0;
303 stem->force = 0;
305 stem++;
307 seg = seg->edge_next;
309 } while ( seg != edge->first );
313 *p_stems = stems;
314 *p_num_stems = num_stems;
316 edges = outline->vert_edges;
317 edge_limit = edges + outline->num_vedges;
318 scale = outline->x_scale;
320 p_stems = &optimizer->vert_stems;
321 p_num_stems = &optimizer->num_vstems;
324 Exit:
326 #ifdef AH_DEBUG_OPTIM
327 AH_Dump_Stems( optimizer );
328 #endif
330 return error;
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 )
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;
346 /* order stems */
347 if ( stem2->opos <= stem1->opos + stem1->owidth )
348 return 0;
350 if ( min < stem2->min_coord )
351 min = stem2->min_coord;
353 if ( max < stem2->max_coord )
354 max = stem2->max_coord;
356 area = ( max-min );
357 if ( 2 * area < area1 && 2 * area < area2 )
358 area = 0;
360 return area;
364 /* compute all springs in an outline */
365 static int
366 optim_compute_springs( AH_Optimizer* optimizer )
368 /* basically, a spring exists between two stems if most of their */
369 /* surface is aligned */
370 FT_Memory memory = optimizer->memory;
372 AH_Stem* stems;
373 AH_Stem* stem_limit;
374 AH_Stem* stem;
375 int dimension;
376 int error = 0;
378 FT_Int* p_num_springs;
379 AH_Spring** p_springs;
382 stems = optimizer->horz_stems;
383 stem_limit = stems + optimizer->num_hstems;
385 p_springs = &optimizer->horz_springs;
386 p_num_springs = &optimizer->num_hsprings;
388 for ( dimension = 1; dimension >= 0; dimension-- )
390 FT_Int num_springs = 0;
391 AH_Spring* springs = 0;
394 /* first of all, count stem springs */
395 for ( stem = stems; stem + 1 < stem_limit; stem++ )
397 AH_Stem* stem2;
400 for ( stem2 = stem+1; stem2 < stem_limit; stem2++ )
401 if ( stem_spring_area( stem, stem2 ) )
402 num_springs++;
405 /* then allocate and build the springs table */
406 if ( num_springs > 0 )
408 AH_Spring* spring;
411 /* allocate table of springs */
412 if ( FT_NEW_ARRAY( springs, num_springs ) )
413 goto Exit;
415 /* fill the springs table */
416 spring = springs;
417 for ( stem = stems; stem+1 < stem_limit; stem++ )
419 AH_Stem* stem2;
420 FT_Pos area;
423 for ( stem2 = stem + 1; stem2 < stem_limit; stem2++ )
425 area = stem_spring_area( stem, stem2 );
426 if ( area )
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;
434 spring++;
439 *p_num_springs = num_springs;
440 *p_springs = springs;
442 stems = optimizer->vert_stems;
443 stem_limit = stems + optimizer->num_vstems;
445 p_springs = &optimizer->vert_springs;
446 p_num_springs = &optimizer->num_vsprings;
449 Exit:
451 #ifdef AH_DEBUG_OPTIM
452 AH_Dump_Springs( optimizer );
453 #endif
455 return error;
459 /*************************************************************************/
460 /*************************************************************************/
461 /*************************************************************************/
462 /**** ****/
463 /**** OPTIMIZE THROUGH MY STRANGE SIMULATED ANNEALING ALGO ;-) ****/
464 /**** ****/
465 /*************************************************************************/
466 /*************************************************************************/
467 /*************************************************************************/
469 #ifndef AH_BRUTE_FORCE
471 /* compute all spring tensions */
472 static void
473 optim_compute_tensions( AH_Optimizer* optimizer )
475 AH_Spring* spring = optimizer->springs;
476 AH_Spring* limit = spring + optimizer->num_springs;
479 for ( ; spring < limit; spring++ )
481 AH_Stem* stem1 = spring->stem1;
482 AH_Stem* stem2 = spring->stem2;
483 FT_Int status;
485 FT_Pos width;
486 FT_Pos tension;
487 FT_Pos sign;
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;
494 sign = 1;
495 if ( tension < 0 )
497 sign = -1;
498 tension = -tension;
501 if ( width <= 0 )
502 tension = 32000;
503 else
504 tension = ( tension << 10 ) / width;
506 tension = -sign * FT_MulFix( tension, optimizer->tension_scale );
507 spring->tension = tension;
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;
517 if ( !status )
518 tension /= 2;
520 if ( ( status & 1 ) == 0 )
521 stem1->force -= tension;
523 if ( ( status & 2 ) == 0 )
524 stem2->force += tension;
529 /* compute all stem movements -- returns 0 if nothing moved */
530 static int
531 optim_compute_stem_movements( AH_Optimizer* optimizer )
533 AH_Stem* stems = optimizer->stems;
534 AH_Stem* limit = stems + optimizer->num_stems;
535 AH_Stem* stem = stems;
536 int moved = 0;
539 /* set initial forces to velocity */
540 for ( stem = stems; stem < limit; stem++ )
542 stem->force = stem->velocity;
543 stem->velocity /= 2; /* XXX: Heuristics */
546 /* compute the sum of forces applied on each stem */
547 optim_compute_tensions( optimizer );
549 #ifdef AH_DEBUG_OPTIM
550 AH_Dump_Springs( optimizer );
551 AH_Dump_Stems2( optimizer );
552 #endif
554 /* now, see whether something can move */
555 for ( stem = stems; stem < limit; stem++ )
557 if ( stem->force > optimizer->tension_threshold )
559 /* there is enough tension to move the stem to the right */
560 if ( stem->pos < stem->max_pos )
562 stem->pos += 64;
563 stem->velocity = stem->force / 2;
564 moved = 1;
566 else
567 stem->velocity = 0;
569 else if ( stem->force < optimizer->tension_threshold )
571 /* there is enough tension to move the stem to the left */
572 if ( stem->pos > stem->min_pos )
574 stem->pos -= 64;
575 stem->velocity = stem->force / 2;
576 moved = 1;
578 else
579 stem->velocity = 0;
583 /* return 0 if nothing moved */
584 return moved;
587 #endif /* AH_BRUTE_FORCE */
590 /* compute current global distortion from springs */
591 static FT_Pos
592 optim_compute_distortion( AH_Optimizer* optimizer )
594 AH_Spring* spring = optimizer->springs;
595 AH_Spring* limit = spring + optimizer->num_springs;
596 FT_Pos distortion = 0;
599 for ( ; spring < limit; spring++ )
601 AH_Stem* stem1 = spring->stem1;
602 AH_Stem* stem2 = spring->stem2;
603 FT_Pos width;
605 width = stem2->pos - ( stem1->pos + stem1->width );
606 width -= spring->owidth;
607 if ( width < 0 )
608 width = -width;
610 distortion += width;
613 return distortion;
617 /* record stems configuration in `best of' history */
618 static void
619 optim_record_configuration( AH_Optimizer* optimizer )
621 FT_Pos distortion;
622 AH_Configuration* configs = optimizer->configs;
623 AH_Configuration* limit = configs + optimizer->num_configs;
624 AH_Configuration* config;
627 distortion = optim_compute_distortion( optimizer );
628 LOG(( "config distortion = %.1f ", FLOAT( distortion * 64 ) ));
630 /* check that we really need to add this configuration to our */
631 /* sorted history */
632 if ( limit > configs && limit[-1].distortion < distortion )
634 LOG(( "ejected\n" ));
635 return;
638 /* add new configuration at the end of the table */
640 int n;
643 config = limit;
644 if ( optimizer->num_configs < AH_MAX_CONFIGS )
645 optimizer->num_configs++;
646 else
647 config--;
649 config->distortion = distortion;
651 for ( n = 0; n < optimizer->num_stems; n++ )
652 config->positions[n] = optimizer->stems[n].pos;
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 )
659 AH_Configuration temp;
662 config--;
663 temp = config[0];
664 config[0] = config[1];
665 config[1] = temp;
667 LOG(( "recorded!\n" ));
671 #ifdef AH_BRUTE_FORCE
673 /* optimize outline in a single direction */
674 static void
675 optim_compute( AH_Optimizer* optimizer )
677 int n;
678 FT_Bool moved;
680 AH_Stem* stem = optimizer->stems;
681 AH_Stem* limit = stem + optimizer->num_stems;
684 /* empty, exit */
685 if ( stem >= limit )
686 return;
688 optimizer->num_configs = 0;
690 stem = optimizer->stems;
691 for ( ; stem < limit; stem++ )
692 stem->pos = stem->min_pos;
696 /* record current configuration */
697 optim_record_configuration( optimizer );
699 /* now change configuration */
700 moved = 0;
701 for ( stem = optimizer->stems; stem < limit; stem++ )
703 if ( stem->pos < stem->max_pos )
705 stem->pos += 64;
706 moved = 1;
707 break;
710 stem->pos = stem->min_pos;
712 } while ( moved );
714 /* now, set the best stem positions */
715 for ( n = 0; n < optimizer->num_stems; n++ )
717 AH_Stem* stem = optimizer->stems + n;
718 FT_Pos pos = optimizer->configs[0].positions[n];
721 stem->edge1->pos = pos;
722 stem->edge2->pos = pos + stem->width;
724 stem->edge1->flags |= ah_edge_done;
725 stem->edge2->flags |= ah_edge_done;
729 #else /* AH_BRUTE_FORCE */
731 /* optimize outline in a single direction */
732 static void
733 optim_compute( AH_Optimizer* optimizer )
735 int n, counter, counter2;
738 optimizer->num_configs = 0;
739 optimizer->tension_scale = 0x80000L;
740 optimizer->tension_threshold = 64;
742 /* record initial configuration threshold */
743 optim_record_configuration( optimizer );
745 counter = 0;
746 for ( counter2 = optimizer->num_stems*8; counter2 >= 0; counter2-- )
748 if ( counter == 0 )
749 counter = 2 * optimizer->num_stems;
751 if ( !optim_compute_stem_movements( optimizer ) )
752 break;
754 optim_record_configuration( optimizer );
756 counter--;
757 if ( counter == 0 )
758 optimizer->tension_scale /= 2;
761 /* now, set the best stem positions */
762 for ( n = 0; n < optimizer->num_stems; n++ )
764 AH_Stem* stem = optimizer->stems + n;
765 FT_Pos pos = optimizer->configs[0].positions[n];
768 stem->edge1->pos = pos;
769 stem->edge2->pos = pos + stem->width;
771 stem->edge1->flags |= ah_edge_done;
772 stem->edge2->flags |= ah_edge_done;
776 #endif /* AH_BRUTE_FORCE */
779 /*************************************************************************/
780 /*************************************************************************/
781 /*************************************************************************/
782 /**** ****/
783 /**** HIGH-LEVEL OPTIMIZER API ****/
784 /**** ****/
785 /*************************************************************************/
786 /*************************************************************************/
787 /*************************************************************************/
790 /* releases the optimization data */
791 void
792 AH_Optimizer_Done( AH_Optimizer* optimizer )
794 if ( optimizer )
796 FT_Memory memory = optimizer->memory;
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 );
808 /* loads the outline into the optimizer */
810 AH_Optimizer_Init( AH_Optimizer* optimizer,
811 AH_Outline* outline,
812 FT_Memory memory )
814 FT_Error error;
817 FT_MEM_SET( optimizer, 0, sizeof ( *optimizer ) );
818 optimizer->outline = outline;
819 optimizer->memory = memory;
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;
828 /* allocate stem positions history and configurations */
830 int n, max_stems;
833 max_stems = optimizer->num_hstems;
834 if ( max_stems < optimizer->num_vstems )
835 max_stems = optimizer->num_vstems;
837 if ( FT_NEW_ARRAY( optimizer->positions, max_stems * AH_MAX_CONFIGS ) )
838 goto Fail;
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;
846 return error;
848 Fail:
849 AH_Optimizer_Done( optimizer );
850 return error;
854 /* compute optimal outline */
855 void
856 AH_Optimizer_Compute( AH_Optimizer* optimizer )
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;
863 if ( optimizer->num_springs > 0 )
865 LOG(( "horizontal optimization ------------------------\n" ));
866 optim_compute( optimizer );
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;
874 if ( optimizer->num_springs )
876 LOG(( "vertical optimization --------------------------\n" ));
877 optim_compute( optimizer );
882 /* END */