]>
Commit | Line | Data |
---|---|---|
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 */ |