1 | /**************************************************************************** |
2 | * |
3 | * pshalgo.c |
4 | * |
5 | * PostScript hinting algorithm (body). |
6 | * |
7 | * Copyright (C) 2001-2023 by |
8 | * David Turner, Robert Wilhelm, and Werner Lemberg. |
9 | * |
10 | * This file is part of the FreeType project, and may only be used |
11 | * modified and distributed under the terms of the FreeType project |
12 | * license, LICENSE.TXT. By continuing to use, modify, or distribute |
13 | * this file you indicate that you have read the license and |
14 | * understand and accept it fully. |
15 | * |
16 | */ |
17 | |
18 | |
19 | #include <freetype/internal/ftobjs.h> |
20 | #include <freetype/internal/ftdebug.h> |
21 | #include <freetype/internal/ftcalc.h> |
22 | #include "pshalgo.h" |
23 | |
24 | #include "pshnterr.h" |
25 | |
26 | |
27 | #undef FT_COMPONENT |
28 | #define FT_COMPONENT pshalgo |
29 | |
30 | |
31 | #ifdef DEBUG_HINTER |
32 | PSH_Hint_Table ps_debug_hint_table = NULL; |
33 | PSH_HintFunc ps_debug_hint_func = NULL; |
34 | PSH_Glyph ps_debug_glyph = NULL; |
35 | #endif |
36 | |
37 | |
38 | #define COMPUTE_INFLEXS /* compute inflection points to optimize `S' */ |
39 | /* and similar glyphs */ |
40 | |
41 | |
42 | /*************************************************************************/ |
43 | /*************************************************************************/ |
44 | /***** *****/ |
45 | /***** BASIC HINTS RECORDINGS *****/ |
46 | /***** *****/ |
47 | /*************************************************************************/ |
48 | /*************************************************************************/ |
49 | |
50 | /* return true if two stem hints overlap */ |
51 | static FT_Int |
52 | psh_hint_overlap( PSH_Hint hint1, |
53 | PSH_Hint hint2 ) |
54 | { |
55 | return ADD_INT( hint1->org_pos, hint1->org_len ) >= hint2->org_pos && |
56 | ADD_INT( hint2->org_pos, hint2->org_len ) >= hint1->org_pos; |
57 | } |
58 | |
59 | |
60 | /* destroy hints table */ |
61 | static void |
62 | psh_hint_table_done( PSH_Hint_Table table, |
63 | FT_Memory memory ) |
64 | { |
65 | FT_FREE( table->zones ); |
66 | table->num_zones = 0; |
67 | table->zone = NULL; |
68 | |
69 | FT_FREE( table->sort ); |
70 | FT_FREE( table->hints ); |
71 | table->num_hints = 0; |
72 | table->max_hints = 0; |
73 | table->sort_global = NULL; |
74 | } |
75 | |
76 | |
77 | /* deactivate all hints in a table */ |
78 | static void |
79 | psh_hint_table_deactivate( PSH_Hint_Table table ) |
80 | { |
81 | FT_UInt count = table->max_hints; |
82 | PSH_Hint hint = table->hints; |
83 | |
84 | |
85 | for ( ; count > 0; count--, hint++ ) |
86 | { |
87 | psh_hint_deactivate( hint ); |
88 | hint->order = -1; |
89 | } |
90 | } |
91 | |
92 | |
93 | /* internal function to record a new hint */ |
94 | static void |
95 | psh_hint_table_record( PSH_Hint_Table table, |
96 | FT_UInt idx ) |
97 | { |
98 | PSH_Hint hint = table->hints + idx; |
99 | |
100 | |
101 | if ( idx >= table->max_hints ) |
102 | { |
103 | FT_TRACE0(( "psh_hint_table_record: invalid hint index %d\n" , idx )); |
104 | return; |
105 | } |
106 | |
107 | /* ignore active hints */ |
108 | if ( psh_hint_is_active( hint ) ) |
109 | return; |
110 | |
111 | psh_hint_activate( hint ); |
112 | |
113 | /* now scan the current active hint set to check */ |
114 | /* whether `hint' overlaps with another hint */ |
115 | { |
116 | PSH_Hint* sorted = table->sort_global; |
117 | FT_UInt count = table->num_hints; |
118 | PSH_Hint hint2; |
119 | |
120 | |
121 | hint->parent = NULL; |
122 | for ( ; count > 0; count--, sorted++ ) |
123 | { |
124 | hint2 = sorted[0]; |
125 | |
126 | if ( psh_hint_overlap( hint, hint2 ) ) |
127 | { |
128 | hint->parent = hint2; |
129 | break; |
130 | } |
131 | } |
132 | } |
133 | |
134 | if ( table->num_hints < table->max_hints ) |
135 | table->sort_global[table->num_hints++] = hint; |
136 | else |
137 | FT_TRACE0(( "psh_hint_table_record: too many sorted hints! BUG!\n" )); |
138 | } |
139 | |
140 | |
141 | static void |
142 | psh_hint_table_record_mask( PSH_Hint_Table table, |
143 | PS_Mask hint_mask ) |
144 | { |
145 | FT_Int mask = 0, val = 0; |
146 | FT_Byte* cursor = hint_mask->bytes; |
147 | FT_UInt idx, limit; |
148 | |
149 | |
150 | limit = hint_mask->num_bits; |
151 | |
152 | for ( idx = 0; idx < limit; idx++ ) |
153 | { |
154 | if ( mask == 0 ) |
155 | { |
156 | val = *cursor++; |
157 | mask = 0x80; |
158 | } |
159 | |
160 | if ( val & mask ) |
161 | psh_hint_table_record( table, idx ); |
162 | |
163 | mask >>= 1; |
164 | } |
165 | } |
166 | |
167 | |
168 | /* create hints table */ |
169 | static FT_Error |
170 | psh_hint_table_init( PSH_Hint_Table table, |
171 | PS_Hint_Table hints, |
172 | PS_Mask_Table hint_masks, |
173 | PS_Mask_Table counter_masks, |
174 | FT_Memory memory ) |
175 | { |
176 | FT_UInt count; |
177 | FT_Error error; |
178 | |
179 | FT_UNUSED( counter_masks ); |
180 | |
181 | |
182 | count = hints->num_hints; |
183 | |
184 | /* allocate our tables */ |
185 | if ( FT_QNEW_ARRAY( table->sort, 2 * count ) || |
186 | FT_QNEW_ARRAY( table->hints, count ) || |
187 | FT_QNEW_ARRAY( table->zones, 2 * count + 1 ) ) |
188 | goto Exit; |
189 | |
190 | table->max_hints = count; |
191 | table->sort_global = FT_OFFSET( table->sort, count ); |
192 | table->num_hints = 0; |
193 | table->num_zones = 0; |
194 | table->zone = NULL; |
195 | |
196 | /* initialize the `table->hints' array */ |
197 | { |
198 | PSH_Hint write = table->hints; |
199 | PS_Hint read = hints->hints; |
200 | |
201 | |
202 | for ( ; count > 0; count--, write++, read++ ) |
203 | { |
204 | write->org_pos = read->pos; |
205 | write->org_len = read->len; |
206 | write->flags = read->flags; |
207 | } |
208 | } |
209 | |
210 | /* we now need to determine the initial `parent' stems; first */ |
211 | /* activate the hints that are given by the initial hint masks */ |
212 | if ( hint_masks ) |
213 | { |
214 | PS_Mask mask = hint_masks->masks; |
215 | |
216 | |
217 | count = hint_masks->num_masks; |
218 | table->hint_masks = hint_masks; |
219 | |
220 | for ( ; count > 0; count--, mask++ ) |
221 | psh_hint_table_record_mask( table, mask ); |
222 | } |
223 | |
224 | /* finally, do a linear parse in case some hints were left alone */ |
225 | if ( table->num_hints != table->max_hints ) |
226 | { |
227 | FT_UInt idx; |
228 | |
229 | |
230 | FT_TRACE0(( "psh_hint_table_init: missing/incorrect hint masks\n" )); |
231 | |
232 | count = table->max_hints; |
233 | for ( idx = 0; idx < count; idx++ ) |
234 | psh_hint_table_record( table, idx ); |
235 | } |
236 | |
237 | Exit: |
238 | return error; |
239 | } |
240 | |
241 | |
242 | static void |
243 | psh_hint_table_activate_mask( PSH_Hint_Table table, |
244 | PS_Mask hint_mask ) |
245 | { |
246 | FT_Int mask = 0, val = 0; |
247 | FT_Byte* cursor = hint_mask->bytes; |
248 | FT_UInt idx, limit, count; |
249 | |
250 | |
251 | limit = hint_mask->num_bits; |
252 | count = 0; |
253 | |
254 | psh_hint_table_deactivate( table ); |
255 | |
256 | for ( idx = 0; idx < limit; idx++ ) |
257 | { |
258 | if ( mask == 0 ) |
259 | { |
260 | val = *cursor++; |
261 | mask = 0x80; |
262 | } |
263 | |
264 | if ( val & mask ) |
265 | { |
266 | PSH_Hint hint = &table->hints[idx]; |
267 | |
268 | |
269 | if ( !psh_hint_is_active( hint ) ) |
270 | { |
271 | FT_UInt count2; |
272 | |
273 | #if 0 |
274 | PSH_Hint* sort = table->sort; |
275 | PSH_Hint hint2; |
276 | |
277 | |
278 | for ( count2 = count; count2 > 0; count2--, sort++ ) |
279 | { |
280 | hint2 = sort[0]; |
281 | if ( psh_hint_overlap( hint, hint2 ) ) |
282 | FT_TRACE0(( "psh_hint_table_activate_mask:" |
283 | " found overlapping hints\n" )) |
284 | } |
285 | #else |
286 | count2 = 0; |
287 | #endif |
288 | |
289 | if ( count2 == 0 ) |
290 | { |
291 | psh_hint_activate( hint ); |
292 | if ( count < table->max_hints ) |
293 | table->sort[count++] = hint; |
294 | else |
295 | FT_TRACE0(( "psh_hint_tableactivate_mask:" |
296 | " too many active hints\n" )); |
297 | } |
298 | } |
299 | } |
300 | |
301 | mask >>= 1; |
302 | } |
303 | table->num_hints = count; |
304 | |
305 | /* now, sort the hints; they are guaranteed to not overlap */ |
306 | /* so we can compare their "org_pos" field directly */ |
307 | { |
308 | FT_UInt i1, i2; |
309 | PSH_Hint hint1, hint2; |
310 | PSH_Hint* sort = table->sort; |
311 | |
312 | |
313 | /* a simple bubble sort will do, since in 99% of cases, the hints */ |
314 | /* will be already sorted -- and the sort will be linear */ |
315 | for ( i1 = 1; i1 < count; i1++ ) |
316 | { |
317 | hint1 = sort[i1]; |
318 | /* this loop stops when i2 wraps around after reaching 0 */ |
319 | for ( i2 = i1 - 1; i2 < i1; i2-- ) |
320 | { |
321 | hint2 = sort[i2]; |
322 | |
323 | if ( hint2->org_pos < hint1->org_pos ) |
324 | break; |
325 | |
326 | sort[i2 + 1] = hint2; |
327 | sort[i2] = hint1; |
328 | } |
329 | } |
330 | } |
331 | } |
332 | |
333 | |
334 | /*************************************************************************/ |
335 | /*************************************************************************/ |
336 | /***** *****/ |
337 | /***** HINTS GRID-FITTING AND OPTIMIZATION *****/ |
338 | /***** *****/ |
339 | /*************************************************************************/ |
340 | /*************************************************************************/ |
341 | |
342 | #if 1 |
343 | static FT_Pos |
344 | psh_dimension_quantize_len( PSH_Dimension dim, |
345 | FT_Pos len, |
346 | FT_Bool do_snapping ) |
347 | { |
348 | if ( len <= 64 ) |
349 | len = 64; |
350 | else |
351 | { |
352 | FT_Pos delta = len - dim->stdw.widths[0].cur; |
353 | |
354 | |
355 | if ( delta < 0 ) |
356 | delta = -delta; |
357 | |
358 | if ( delta < 40 ) |
359 | { |
360 | len = dim->stdw.widths[0].cur; |
361 | if ( len < 48 ) |
362 | len = 48; |
363 | } |
364 | |
365 | if ( len < 3 * 64 ) |
366 | { |
367 | delta = ( len & 63 ); |
368 | len &= -64; |
369 | |
370 | if ( delta < 10 ) |
371 | len += delta; |
372 | |
373 | else if ( delta < 32 ) |
374 | len += 10; |
375 | |
376 | else if ( delta < 54 ) |
377 | len += 54; |
378 | |
379 | else |
380 | len += delta; |
381 | } |
382 | else |
383 | len = FT_PIX_ROUND( len ); |
384 | } |
385 | |
386 | if ( do_snapping ) |
387 | len = FT_PIX_ROUND( len ); |
388 | |
389 | return len; |
390 | } |
391 | #endif /* 0 */ |
392 | |
393 | |
394 | #ifdef DEBUG_HINTER |
395 | |
396 | static void |
397 | ps_simple_scale( PSH_Hint_Table table, |
398 | FT_Fixed scale, |
399 | FT_Fixed delta, |
400 | FT_Int dimension ) |
401 | { |
402 | FT_UInt count; |
403 | |
404 | |
405 | for ( count = 0; count < table->max_hints; count++ ) |
406 | { |
407 | PSH_Hint hint = table->hints + count; |
408 | |
409 | |
410 | hint->cur_pos = FT_MulFix( hint->org_pos, scale ) + delta; |
411 | hint->cur_len = FT_MulFix( hint->org_len, scale ); |
412 | |
413 | if ( ps_debug_hint_func ) |
414 | ps_debug_hint_func( hint, dimension ); |
415 | } |
416 | } |
417 | |
418 | #endif /* DEBUG_HINTER */ |
419 | |
420 | |
421 | static FT_Fixed |
422 | psh_hint_snap_stem_side_delta( FT_Fixed pos, |
423 | FT_Fixed len ) |
424 | { |
425 | FT_Fixed delta1 = FT_PIX_ROUND( pos ) - pos; |
426 | FT_Fixed delta2 = FT_PIX_ROUND( pos + len ) - pos - len; |
427 | |
428 | |
429 | if ( FT_ABS( delta1 ) <= FT_ABS( delta2 ) ) |
430 | return delta1; |
431 | else |
432 | return delta2; |
433 | } |
434 | |
435 | |
436 | static void |
437 | psh_hint_align( PSH_Hint hint, |
438 | PSH_Globals globals, |
439 | FT_Int dimension, |
440 | PSH_Glyph glyph ) |
441 | { |
442 | PSH_Dimension dim = &globals->dimension[dimension]; |
443 | FT_Fixed scale = dim->scale_mult; |
444 | FT_Fixed delta = dim->scale_delta; |
445 | |
446 | |
447 | if ( !psh_hint_is_fitted( hint ) ) |
448 | { |
449 | FT_Pos pos = FT_MulFix( hint->org_pos, scale ) + delta; |
450 | FT_Pos len = FT_MulFix( hint->org_len, scale ); |
451 | |
452 | FT_Int do_snapping; |
453 | FT_Pos fit_len; |
454 | PSH_AlignmentRec align; |
455 | |
456 | |
457 | /* ignore stem alignments when requested through the hint flags */ |
458 | if ( ( dimension == 0 && !glyph->do_horz_hints ) || |
459 | ( dimension == 1 && !glyph->do_vert_hints ) ) |
460 | { |
461 | hint->cur_pos = pos; |
462 | hint->cur_len = len; |
463 | |
464 | psh_hint_set_fitted( hint ); |
465 | return; |
466 | } |
467 | |
468 | /* perform stem snapping when requested - this is necessary |
469 | * for monochrome and LCD hinting modes only |
470 | */ |
471 | do_snapping = ( dimension == 0 && glyph->do_horz_snapping ) || |
472 | ( dimension == 1 && glyph->do_vert_snapping ); |
473 | |
474 | hint->cur_len = fit_len = len; |
475 | |
476 | /* check blue zones for horizontal stems */ |
477 | align.align = PSH_BLUE_ALIGN_NONE; |
478 | align.align_bot = align.align_top = 0; |
479 | |
480 | if ( dimension == 1 ) |
481 | psh_blues_snap_stem( &globals->blues, |
482 | ADD_INT( hint->org_pos, hint->org_len ), |
483 | hint->org_pos, |
484 | &align ); |
485 | |
486 | switch ( align.align ) |
487 | { |
488 | case PSH_BLUE_ALIGN_TOP: |
489 | /* the top of the stem is aligned against a blue zone */ |
490 | hint->cur_pos = align.align_top - fit_len; |
491 | break; |
492 | |
493 | case PSH_BLUE_ALIGN_BOT: |
494 | /* the bottom of the stem is aligned against a blue zone */ |
495 | hint->cur_pos = align.align_bot; |
496 | break; |
497 | |
498 | case PSH_BLUE_ALIGN_TOP | PSH_BLUE_ALIGN_BOT: |
499 | /* both edges of the stem are aligned against blue zones */ |
500 | hint->cur_pos = align.align_bot; |
501 | hint->cur_len = align.align_top - align.align_bot; |
502 | break; |
503 | |
504 | default: |
505 | { |
506 | PSH_Hint parent = hint->parent; |
507 | |
508 | |
509 | if ( parent ) |
510 | { |
511 | FT_Pos par_org_center, par_cur_center; |
512 | FT_Pos cur_org_center, cur_delta; |
513 | |
514 | |
515 | /* ensure that parent is already fitted */ |
516 | if ( !psh_hint_is_fitted( parent ) ) |
517 | psh_hint_align( parent, globals, dimension, glyph ); |
518 | |
519 | /* keep original relation between hints, that is, use the */ |
520 | /* scaled distance between the centers of the hints to */ |
521 | /* compute the new position */ |
522 | par_org_center = parent->org_pos + ( parent->org_len >> 1 ); |
523 | par_cur_center = parent->cur_pos + ( parent->cur_len >> 1 ); |
524 | cur_org_center = hint->org_pos + ( hint->org_len >> 1 ); |
525 | |
526 | cur_delta = FT_MulFix( cur_org_center - par_org_center, scale ); |
527 | pos = par_cur_center + cur_delta - ( len >> 1 ); |
528 | } |
529 | |
530 | hint->cur_pos = pos; |
531 | hint->cur_len = fit_len; |
532 | |
533 | /* Stem adjustment tries to snap stem widths to standard |
534 | * ones. This is important to prevent unpleasant rounding |
535 | * artefacts. |
536 | */ |
537 | if ( glyph->do_stem_adjust ) |
538 | { |
539 | if ( len <= 64 ) |
540 | { |
541 | /* the stem is less than one pixel; we will center it |
542 | * around the nearest pixel center |
543 | */ |
544 | if ( len >= 32 ) |
545 | { |
546 | /* This is a special case where we also widen the stem |
547 | * and align it to the pixel grid. |
548 | * |
549 | * stem_center = pos + (len/2) |
550 | * nearest_pixel_center = FT_ROUND(stem_center-32)+32 |
551 | * new_pos = nearest_pixel_center-32 |
552 | * = FT_ROUND(stem_center-32) |
553 | * = FT_FLOOR(stem_center-32+32) |
554 | * = FT_FLOOR(stem_center) |
555 | * new_len = 64 |
556 | */ |
557 | pos = FT_PIX_FLOOR( pos + ( len >> 1 ) ); |
558 | len = 64; |
559 | } |
560 | else if ( len > 0 ) |
561 | { |
562 | /* This is a very small stem; we simply align it to the |
563 | * pixel grid, trying to find the minimum displacement. |
564 | * |
565 | * left = pos |
566 | * right = pos + len |
567 | * left_nearest_edge = ROUND(pos) |
568 | * right_nearest_edge = ROUND(right) |
569 | * |
570 | * if ( ABS(left_nearest_edge - left) <= |
571 | * ABS(right_nearest_edge - right) ) |
572 | * new_pos = left |
573 | * else |
574 | * new_pos = right |
575 | */ |
576 | FT_Pos left_nearest = FT_PIX_ROUND( pos ); |
577 | FT_Pos right_nearest = FT_PIX_ROUND( pos + len ); |
578 | FT_Pos left_disp = left_nearest - pos; |
579 | FT_Pos right_disp = right_nearest - ( pos + len ); |
580 | |
581 | |
582 | if ( left_disp < 0 ) |
583 | left_disp = -left_disp; |
584 | if ( right_disp < 0 ) |
585 | right_disp = -right_disp; |
586 | if ( left_disp <= right_disp ) |
587 | pos = left_nearest; |
588 | else |
589 | pos = right_nearest; |
590 | } |
591 | else |
592 | { |
593 | /* this is a ghost stem; we simply round it */ |
594 | pos = FT_PIX_ROUND( pos ); |
595 | } |
596 | } |
597 | else |
598 | { |
599 | len = psh_dimension_quantize_len( dim, len, 0 ); |
600 | } |
601 | } |
602 | |
603 | /* now that we have a good hinted stem width, try to position */ |
604 | /* the stem along a pixel grid integer coordinate */ |
605 | hint->cur_pos = pos + psh_hint_snap_stem_side_delta( pos, len ); |
606 | hint->cur_len = len; |
607 | } |
608 | } |
609 | |
610 | if ( do_snapping ) |
611 | { |
612 | pos = hint->cur_pos; |
613 | len = hint->cur_len; |
614 | |
615 | if ( len < 64 ) |
616 | len = 64; |
617 | else |
618 | len = FT_PIX_ROUND( len ); |
619 | |
620 | switch ( align.align ) |
621 | { |
622 | case PSH_BLUE_ALIGN_TOP: |
623 | hint->cur_pos = align.align_top - len; |
624 | hint->cur_len = len; |
625 | break; |
626 | |
627 | case PSH_BLUE_ALIGN_BOT: |
628 | hint->cur_len = len; |
629 | break; |
630 | |
631 | case PSH_BLUE_ALIGN_BOT | PSH_BLUE_ALIGN_TOP: |
632 | /* don't touch */ |
633 | break; |
634 | |
635 | |
636 | default: |
637 | hint->cur_len = len; |
638 | if ( len & 64 ) |
639 | pos = FT_PIX_FLOOR( pos + ( len >> 1 ) ) + 32; |
640 | else |
641 | pos = FT_PIX_ROUND( pos + ( len >> 1 ) ); |
642 | |
643 | hint->cur_pos = pos - ( len >> 1 ); |
644 | hint->cur_len = len; |
645 | } |
646 | } |
647 | |
648 | psh_hint_set_fitted( hint ); |
649 | |
650 | #ifdef DEBUG_HINTER |
651 | if ( ps_debug_hint_func ) |
652 | ps_debug_hint_func( hint, dimension ); |
653 | #endif |
654 | } |
655 | } |
656 | |
657 | |
658 | #if 0 /* not used for now, experimental */ |
659 | |
660 | /* |
661 | * A variant to perform "light" hinting (i.e. FT_RENDER_MODE_LIGHT) |
662 | * of stems |
663 | */ |
664 | static void |
665 | psh_hint_align_light( PSH_Hint hint, |
666 | PSH_Globals globals, |
667 | FT_Int dimension, |
668 | PSH_Glyph glyph ) |
669 | { |
670 | PSH_Dimension dim = &globals->dimension[dimension]; |
671 | FT_Fixed scale = dim->scale_mult; |
672 | FT_Fixed delta = dim->scale_delta; |
673 | |
674 | |
675 | if ( !psh_hint_is_fitted( hint ) ) |
676 | { |
677 | FT_Pos pos = FT_MulFix( hint->org_pos, scale ) + delta; |
678 | FT_Pos len = FT_MulFix( hint->org_len, scale ); |
679 | |
680 | FT_Pos fit_len; |
681 | |
682 | PSH_AlignmentRec align; |
683 | |
684 | |
685 | /* ignore stem alignments when requested through the hint flags */ |
686 | if ( ( dimension == 0 && !glyph->do_horz_hints ) || |
687 | ( dimension == 1 && !glyph->do_vert_hints ) ) |
688 | { |
689 | hint->cur_pos = pos; |
690 | hint->cur_len = len; |
691 | |
692 | psh_hint_set_fitted( hint ); |
693 | return; |
694 | } |
695 | |
696 | fit_len = len; |
697 | |
698 | hint->cur_len = fit_len; |
699 | |
700 | /* check blue zones for horizontal stems */ |
701 | align.align = PSH_BLUE_ALIGN_NONE; |
702 | align.align_bot = align.align_top = 0; |
703 | |
704 | if ( dimension == 1 ) |
705 | psh_blues_snap_stem( &globals->blues, |
706 | ADD_INT( hint->org_pos, hint->org_len ), |
707 | hint->org_pos, |
708 | &align ); |
709 | |
710 | switch ( align.align ) |
711 | { |
712 | case PSH_BLUE_ALIGN_TOP: |
713 | /* the top of the stem is aligned against a blue zone */ |
714 | hint->cur_pos = align.align_top - fit_len; |
715 | break; |
716 | |
717 | case PSH_BLUE_ALIGN_BOT: |
718 | /* the bottom of the stem is aligned against a blue zone */ |
719 | hint->cur_pos = align.align_bot; |
720 | break; |
721 | |
722 | case PSH_BLUE_ALIGN_TOP | PSH_BLUE_ALIGN_BOT: |
723 | /* both edges of the stem are aligned against blue zones */ |
724 | hint->cur_pos = align.align_bot; |
725 | hint->cur_len = align.align_top - align.align_bot; |
726 | break; |
727 | |
728 | default: |
729 | { |
730 | PSH_Hint parent = hint->parent; |
731 | |
732 | |
733 | if ( parent ) |
734 | { |
735 | FT_Pos par_org_center, par_cur_center; |
736 | FT_Pos cur_org_center, cur_delta; |
737 | |
738 | |
739 | /* ensure that parent is already fitted */ |
740 | if ( !psh_hint_is_fitted( parent ) ) |
741 | psh_hint_align_light( parent, globals, dimension, glyph ); |
742 | |
743 | par_org_center = parent->org_pos + ( parent->org_len / 2 ); |
744 | par_cur_center = parent->cur_pos + ( parent->cur_len / 2 ); |
745 | cur_org_center = hint->org_pos + ( hint->org_len / 2 ); |
746 | |
747 | cur_delta = FT_MulFix( cur_org_center - par_org_center, scale ); |
748 | pos = par_cur_center + cur_delta - ( len >> 1 ); |
749 | } |
750 | |
751 | /* Stems less than one pixel wide are easy -- we want to |
752 | * make them as dark as possible, so they must fall within |
753 | * one pixel. If the stem is split between two pixels |
754 | * then snap the edge that is nearer to the pixel boundary |
755 | * to the pixel boundary. |
756 | */ |
757 | if ( len <= 64 ) |
758 | { |
759 | if ( ( pos + len + 63 ) / 64 != pos / 64 + 1 ) |
760 | pos += psh_hint_snap_stem_side_delta ( pos, len ); |
761 | } |
762 | |
763 | /* Position stems other to minimize the amount of mid-grays. |
764 | * There are, in general, two positions that do this, |
765 | * illustrated as A) and B) below. |
766 | * |
767 | * + + + + |
768 | * |
769 | * A) |--------------------------------| |
770 | * B) |--------------------------------| |
771 | * C) |--------------------------------| |
772 | * |
773 | * Position A) (split the excess stem equally) should be better |
774 | * for stems of width N + f where f < 0.5. |
775 | * |
776 | * Position B) (split the deficiency equally) should be better |
777 | * for stems of width N + f where f > 0.5. |
778 | * |
779 | * It turns out though that minimizing the total number of lit |
780 | * pixels is also important, so position C), with one edge |
781 | * aligned with a pixel boundary is actually preferable |
782 | * to A). There are also more possible positions for C) than |
783 | * for A) or B), so it involves less distortion of the overall |
784 | * character shape. |
785 | */ |
786 | else /* len > 64 */ |
787 | { |
788 | FT_Fixed frac_len = len & 63; |
789 | FT_Fixed center = pos + ( len >> 1 ); |
790 | FT_Fixed delta_a, delta_b; |
791 | |
792 | |
793 | if ( ( len / 64 ) & 1 ) |
794 | { |
795 | delta_a = FT_PIX_FLOOR( center ) + 32 - center; |
796 | delta_b = FT_PIX_ROUND( center ) - center; |
797 | } |
798 | else |
799 | { |
800 | delta_a = FT_PIX_ROUND( center ) - center; |
801 | delta_b = FT_PIX_FLOOR( center ) + 32 - center; |
802 | } |
803 | |
804 | /* We choose between B) and C) above based on the amount |
805 | * of fractional stem width; for small amounts, choose |
806 | * C) always, for large amounts, B) always, and inbetween, |
807 | * pick whichever one involves less stem movement. |
808 | */ |
809 | if ( frac_len < 32 ) |
810 | { |
811 | pos += psh_hint_snap_stem_side_delta ( pos, len ); |
812 | } |
813 | else if ( frac_len < 48 ) |
814 | { |
815 | FT_Fixed side_delta = psh_hint_snap_stem_side_delta ( pos, |
816 | len ); |
817 | |
818 | if ( FT_ABS( side_delta ) < FT_ABS( delta_b ) ) |
819 | pos += side_delta; |
820 | else |
821 | pos += delta_b; |
822 | } |
823 | else |
824 | { |
825 | pos += delta_b; |
826 | } |
827 | } |
828 | |
829 | hint->cur_pos = pos; |
830 | } |
831 | } /* switch */ |
832 | |
833 | psh_hint_set_fitted( hint ); |
834 | |
835 | #ifdef DEBUG_HINTER |
836 | if ( ps_debug_hint_func ) |
837 | ps_debug_hint_func( hint, dimension ); |
838 | #endif |
839 | } |
840 | } |
841 | |
842 | #endif /* 0 */ |
843 | |
844 | |
845 | static void |
846 | psh_hint_table_align_hints( PSH_Hint_Table table, |
847 | PSH_Globals globals, |
848 | FT_Int dimension, |
849 | PSH_Glyph glyph ) |
850 | { |
851 | PSH_Hint hint; |
852 | FT_UInt count; |
853 | |
854 | #ifdef DEBUG_HINTER |
855 | |
856 | PSH_Dimension dim = &globals->dimension[dimension]; |
857 | FT_Fixed scale = dim->scale_mult; |
858 | FT_Fixed delta = dim->scale_delta; |
859 | |
860 | |
861 | if ( ps_debug_no_vert_hints && dimension == 0 ) |
862 | { |
863 | ps_simple_scale( table, scale, delta, dimension ); |
864 | return; |
865 | } |
866 | |
867 | if ( ps_debug_no_horz_hints && dimension == 1 ) |
868 | { |
869 | ps_simple_scale( table, scale, delta, dimension ); |
870 | return; |
871 | } |
872 | |
873 | #endif /* DEBUG_HINTER */ |
874 | |
875 | hint = table->hints; |
876 | count = table->max_hints; |
877 | |
878 | for ( ; count > 0; count--, hint++ ) |
879 | psh_hint_align( hint, globals, dimension, glyph ); |
880 | } |
881 | |
882 | |
883 | /*************************************************************************/ |
884 | /*************************************************************************/ |
885 | /***** *****/ |
886 | /***** POINTS INTERPOLATION ROUTINES *****/ |
887 | /***** *****/ |
888 | /*************************************************************************/ |
889 | /*************************************************************************/ |
890 | |
891 | #define xxDEBUG_ZONES |
892 | |
893 | |
894 | #ifdef DEBUG_ZONES |
895 | |
896 | #include FT_CONFIG_STANDARD_LIBRARY_H |
897 | |
898 | static void |
899 | psh_print_zone( PSH_Zone zone ) |
900 | { |
901 | printf( "zone [scale,delta,min,max] = [%.5f,%.2f,%d,%d]\n" , |
902 | zone->scale / 65536.0, |
903 | zone->delta / 64.0, |
904 | zone->min, |
905 | zone->max ); |
906 | } |
907 | |
908 | #endif /* DEBUG_ZONES */ |
909 | |
910 | |
911 | /*************************************************************************/ |
912 | /*************************************************************************/ |
913 | /***** *****/ |
914 | /***** HINTER GLYPH MANAGEMENT *****/ |
915 | /***** *****/ |
916 | /*************************************************************************/ |
917 | /*************************************************************************/ |
918 | |
919 | #define psh_corner_is_flat ft_corner_is_flat |
920 | #define psh_corner_orientation ft_corner_orientation |
921 | |
922 | |
923 | #ifdef COMPUTE_INFLEXS |
924 | |
925 | /* compute all inflex points in a given glyph */ |
926 | static void |
927 | psh_glyph_compute_inflections( PSH_Glyph glyph ) |
928 | { |
929 | FT_UInt n; |
930 | |
931 | |
932 | for ( n = 0; n < glyph->num_contours; n++ ) |
933 | { |
934 | PSH_Point first, start, end, before, after; |
935 | FT_Pos in_x, in_y, out_x, out_y; |
936 | FT_Int orient_prev, orient_cur; |
937 | FT_Int finished = 0; |
938 | |
939 | |
940 | /* we need at least 4 points to create an inflection point */ |
941 | if ( glyph->contours[n].count < 4 ) |
942 | continue; |
943 | |
944 | /* compute first segment in contour */ |
945 | first = glyph->contours[n].start; |
946 | |
947 | start = end = first; |
948 | do |
949 | { |
950 | end = end->next; |
951 | if ( end == first ) |
952 | goto Skip; |
953 | |
954 | in_x = end->org_u - start->org_u; |
955 | in_y = end->org_v - start->org_v; |
956 | |
957 | } while ( in_x == 0 && in_y == 0 ); |
958 | |
959 | /* extend the segment start whenever possible */ |
960 | before = start; |
961 | do |
962 | { |
963 | do |
964 | { |
965 | start = before; |
966 | before = before->prev; |
967 | if ( before == first ) |
968 | goto Skip; |
969 | |
970 | out_x = start->org_u - before->org_u; |
971 | out_y = start->org_v - before->org_v; |
972 | |
973 | } while ( out_x == 0 && out_y == 0 ); |
974 | |
975 | orient_prev = psh_corner_orientation( in_x, in_y, out_x, out_y ); |
976 | |
977 | } while ( orient_prev == 0 ); |
978 | |
979 | first = start; |
980 | in_x = out_x; |
981 | in_y = out_y; |
982 | |
983 | /* now, process all segments in the contour */ |
984 | do |
985 | { |
986 | /* first, extend current segment's end whenever possible */ |
987 | after = end; |
988 | do |
989 | { |
990 | do |
991 | { |
992 | end = after; |
993 | after = after->next; |
994 | if ( after == first ) |
995 | finished = 1; |
996 | |
997 | out_x = after->org_u - end->org_u; |
998 | out_y = after->org_v - end->org_v; |
999 | |
1000 | } while ( out_x == 0 && out_y == 0 ); |
1001 | |
1002 | orient_cur = psh_corner_orientation( in_x, in_y, out_x, out_y ); |
1003 | |
1004 | } while ( orient_cur == 0 ); |
1005 | |
1006 | if ( ( orient_cur ^ orient_prev ) < 0 ) |
1007 | { |
1008 | do |
1009 | { |
1010 | psh_point_set_inflex( start ); |
1011 | start = start->next; |
1012 | } |
1013 | while ( start != end ); |
1014 | |
1015 | psh_point_set_inflex( start ); |
1016 | } |
1017 | |
1018 | start = end; |
1019 | end = after; |
1020 | orient_prev = orient_cur; |
1021 | in_x = out_x; |
1022 | in_y = out_y; |
1023 | |
1024 | } while ( !finished ); |
1025 | |
1026 | Skip: |
1027 | ; |
1028 | } |
1029 | } |
1030 | |
1031 | #endif /* COMPUTE_INFLEXS */ |
1032 | |
1033 | |
1034 | static void |
1035 | psh_glyph_done( PSH_Glyph glyph ) |
1036 | { |
1037 | FT_Memory memory = glyph->memory; |
1038 | |
1039 | |
1040 | psh_hint_table_done( &glyph->hint_tables[1], memory ); |
1041 | psh_hint_table_done( &glyph->hint_tables[0], memory ); |
1042 | |
1043 | FT_FREE( glyph->points ); |
1044 | FT_FREE( glyph->contours ); |
1045 | |
1046 | glyph->num_points = 0; |
1047 | glyph->num_contours = 0; |
1048 | |
1049 | glyph->memory = NULL; |
1050 | } |
1051 | |
1052 | |
1053 | static PSH_Dir |
1054 | psh_compute_dir( FT_Pos dx, |
1055 | FT_Pos dy ) |
1056 | { |
1057 | FT_Pos ax, ay; |
1058 | PSH_Dir result = PSH_DIR_NONE; |
1059 | |
1060 | |
1061 | ax = FT_ABS( dx ); |
1062 | ay = FT_ABS( dy ); |
1063 | |
1064 | if ( ay * 12 < ax ) |
1065 | { |
1066 | /* |dy| <<< |dx| means a near-horizontal segment */ |
1067 | result = ( dx >= 0 ) ? PSH_DIR_RIGHT : PSH_DIR_LEFT; |
1068 | } |
1069 | else if ( ax * 12 < ay ) |
1070 | { |
1071 | /* |dx| <<< |dy| means a near-vertical segment */ |
1072 | result = ( dy >= 0 ) ? PSH_DIR_UP : PSH_DIR_DOWN; |
1073 | } |
1074 | |
1075 | return result; |
1076 | } |
1077 | |
1078 | |
1079 | /* load outline point coordinates into hinter glyph */ |
1080 | static void |
1081 | psh_glyph_load_points( PSH_Glyph glyph, |
1082 | FT_Int dimension ) |
1083 | { |
1084 | FT_Vector* vec = glyph->outline->points; |
1085 | PSH_Point point = glyph->points; |
1086 | FT_UInt count = glyph->num_points; |
1087 | |
1088 | |
1089 | for ( ; count > 0; count--, point++, vec++ ) |
1090 | { |
1091 | point->flags2 = 0; |
1092 | point->hint = NULL; |
1093 | if ( dimension == 0 ) |
1094 | { |
1095 | point->org_u = vec->x; |
1096 | point->org_v = vec->y; |
1097 | } |
1098 | else |
1099 | { |
1100 | point->org_u = vec->y; |
1101 | point->org_v = vec->x; |
1102 | } |
1103 | |
1104 | #ifdef DEBUG_HINTER |
1105 | point->org_x = vec->x; |
1106 | point->org_y = vec->y; |
1107 | #endif |
1108 | |
1109 | } |
1110 | } |
1111 | |
1112 | |
1113 | /* save hinted point coordinates back to outline */ |
1114 | static void |
1115 | psh_glyph_save_points( PSH_Glyph glyph, |
1116 | FT_Int dimension ) |
1117 | { |
1118 | FT_UInt n; |
1119 | PSH_Point point = glyph->points; |
1120 | FT_Vector* vec = glyph->outline->points; |
1121 | char* tags = glyph->outline->tags; |
1122 | |
1123 | |
1124 | for ( n = 0; n < glyph->num_points; n++ ) |
1125 | { |
1126 | if ( dimension == 0 ) |
1127 | vec[n].x = point->cur_u; |
1128 | else |
1129 | vec[n].y = point->cur_u; |
1130 | |
1131 | if ( psh_point_is_strong( point ) ) |
1132 | tags[n] |= (char)( ( dimension == 0 ) ? 32 : 64 ); |
1133 | |
1134 | #ifdef DEBUG_HINTER |
1135 | |
1136 | if ( dimension == 0 ) |
1137 | { |
1138 | point->cur_x = point->cur_u; |
1139 | point->flags_x = point->flags2 | point->flags; |
1140 | } |
1141 | else |
1142 | { |
1143 | point->cur_y = point->cur_u; |
1144 | point->flags_y = point->flags2 | point->flags; |
1145 | } |
1146 | |
1147 | #endif |
1148 | |
1149 | point++; |
1150 | } |
1151 | } |
1152 | |
1153 | |
1154 | static FT_Error |
1155 | psh_glyph_init( PSH_Glyph glyph, |
1156 | FT_Outline* outline, |
1157 | PS_Hints ps_hints, |
1158 | PSH_Globals globals ) |
1159 | { |
1160 | FT_Error error; |
1161 | FT_Memory memory; |
1162 | |
1163 | |
1164 | /* clear all fields */ |
1165 | FT_ZERO( glyph ); |
1166 | |
1167 | memory = glyph->memory = globals->memory; |
1168 | |
1169 | /* allocate and setup points + contours arrays */ |
1170 | if ( FT_QNEW_ARRAY( glyph->points, outline->n_points ) || |
1171 | FT_QNEW_ARRAY( glyph->contours, outline->n_contours ) ) |
1172 | goto Exit; |
1173 | |
1174 | glyph->num_points = (FT_UInt)outline->n_points; |
1175 | glyph->num_contours = (FT_UInt)outline->n_contours; |
1176 | |
1177 | { |
1178 | FT_UInt first = 0, next, n; |
1179 | PSH_Point points = glyph->points; |
1180 | PSH_Contour contour = glyph->contours; |
1181 | |
1182 | |
1183 | for ( n = 0; n < glyph->num_contours; n++ ) |
1184 | { |
1185 | FT_UInt count; |
1186 | PSH_Point point; |
1187 | |
1188 | |
1189 | next = (FT_UInt)outline->contours[n] + 1; |
1190 | count = next - first; |
1191 | |
1192 | contour->start = points + first; |
1193 | contour->count = count; |
1194 | |
1195 | if ( count > 0 ) |
1196 | { |
1197 | point = points + first; |
1198 | |
1199 | point->prev = points + next - 1; |
1200 | point->contour = contour; |
1201 | |
1202 | for ( ; count > 1; count-- ) |
1203 | { |
1204 | point[0].next = point + 1; |
1205 | point[1].prev = point; |
1206 | point++; |
1207 | point->contour = contour; |
1208 | } |
1209 | point->next = points + first; |
1210 | } |
1211 | |
1212 | contour++; |
1213 | first = next; |
1214 | } |
1215 | } |
1216 | |
1217 | { |
1218 | PSH_Point points = glyph->points; |
1219 | PSH_Point point = points; |
1220 | FT_Vector* vec = outline->points; |
1221 | FT_UInt n; |
1222 | |
1223 | |
1224 | for ( n = 0; n < glyph->num_points; n++, point++ ) |
1225 | { |
1226 | FT_Int n_prev = (FT_Int)( point->prev - points ); |
1227 | FT_Int n_next = (FT_Int)( point->next - points ); |
1228 | FT_Pos dxi, dyi, dxo, dyo; |
1229 | |
1230 | |
1231 | point->flags = 0; |
1232 | if ( !( outline->tags[n] & FT_CURVE_TAG_ON ) ) |
1233 | psh_point_set_off( point ); |
1234 | |
1235 | dxi = vec[n].x - vec[n_prev].x; |
1236 | dyi = vec[n].y - vec[n_prev].y; |
1237 | |
1238 | point->dir_in = psh_compute_dir( dxi, dyi ); |
1239 | |
1240 | dxo = vec[n_next].x - vec[n].x; |
1241 | dyo = vec[n_next].y - vec[n].y; |
1242 | |
1243 | point->dir_out = psh_compute_dir( dxo, dyo ); |
1244 | |
1245 | /* detect smooth points */ |
1246 | if ( psh_point_is_off( point ) ) |
1247 | psh_point_set_smooth( point ); |
1248 | |
1249 | else if ( point->dir_in == point->dir_out ) |
1250 | { |
1251 | if ( point->dir_out != PSH_DIR_NONE || |
1252 | psh_corner_is_flat( dxi, dyi, dxo, dyo ) ) |
1253 | psh_point_set_smooth( point ); |
1254 | } |
1255 | } |
1256 | } |
1257 | |
1258 | glyph->outline = outline; |
1259 | glyph->globals = globals; |
1260 | |
1261 | #ifdef COMPUTE_INFLEXS |
1262 | psh_glyph_load_points( glyph, 0 ); |
1263 | psh_glyph_compute_inflections( glyph ); |
1264 | #endif /* COMPUTE_INFLEXS */ |
1265 | |
1266 | /* now deal with hints tables */ |
1267 | error = psh_hint_table_init( &glyph->hint_tables [0], |
1268 | &ps_hints->dimension[0].hints, |
1269 | &ps_hints->dimension[0].masks, |
1270 | &ps_hints->dimension[0].counters, |
1271 | memory ); |
1272 | if ( error ) |
1273 | goto Exit; |
1274 | |
1275 | error = psh_hint_table_init( &glyph->hint_tables [1], |
1276 | &ps_hints->dimension[1].hints, |
1277 | &ps_hints->dimension[1].masks, |
1278 | &ps_hints->dimension[1].counters, |
1279 | memory ); |
1280 | if ( error ) |
1281 | goto Exit; |
1282 | |
1283 | Exit: |
1284 | return error; |
1285 | } |
1286 | |
1287 | |
1288 | /* compute all extrema in a glyph for a given dimension */ |
1289 | static void |
1290 | psh_glyph_compute_extrema( PSH_Glyph glyph ) |
1291 | { |
1292 | FT_UInt n; |
1293 | |
1294 | |
1295 | /* first of all, compute all local extrema */ |
1296 | for ( n = 0; n < glyph->num_contours; n++ ) |
1297 | { |
1298 | PSH_Point first = glyph->contours[n].start; |
1299 | PSH_Point point, before, after; |
1300 | |
1301 | |
1302 | if ( glyph->contours[n].count == 0 ) |
1303 | continue; |
1304 | |
1305 | point = first; |
1306 | before = point; |
1307 | |
1308 | do |
1309 | { |
1310 | before = before->prev; |
1311 | if ( before == first ) |
1312 | goto Skip; |
1313 | |
1314 | } while ( before->org_u == point->org_u ); |
1315 | |
1316 | first = point = before->next; |
1317 | |
1318 | for (;;) |
1319 | { |
1320 | after = point; |
1321 | do |
1322 | { |
1323 | after = after->next; |
1324 | if ( after == first ) |
1325 | goto Next; |
1326 | |
1327 | } while ( after->org_u == point->org_u ); |
1328 | |
1329 | if ( before->org_u < point->org_u ) |
1330 | { |
1331 | if ( after->org_u < point->org_u ) |
1332 | { |
1333 | /* local maximum */ |
1334 | goto Extremum; |
1335 | } |
1336 | } |
1337 | else /* before->org_u > point->org_u */ |
1338 | { |
1339 | if ( after->org_u > point->org_u ) |
1340 | { |
1341 | /* local minimum */ |
1342 | Extremum: |
1343 | do |
1344 | { |
1345 | psh_point_set_extremum( point ); |
1346 | point = point->next; |
1347 | |
1348 | } while ( point != after ); |
1349 | } |
1350 | } |
1351 | |
1352 | before = after->prev; |
1353 | point = after; |
1354 | |
1355 | } /* for */ |
1356 | |
1357 | Next: |
1358 | ; |
1359 | } |
1360 | |
1361 | /* for each extremum, determine its direction along the */ |
1362 | /* orthogonal axis */ |
1363 | for ( n = 0; n < glyph->num_points; n++ ) |
1364 | { |
1365 | PSH_Point point, before, after; |
1366 | |
1367 | |
1368 | point = &glyph->points[n]; |
1369 | before = point; |
1370 | after = point; |
1371 | |
1372 | if ( psh_point_is_extremum( point ) ) |
1373 | { |
1374 | do |
1375 | { |
1376 | before = before->prev; |
1377 | if ( before == point ) |
1378 | goto Skip; |
1379 | |
1380 | } while ( before->org_v == point->org_v ); |
1381 | |
1382 | do |
1383 | { |
1384 | after = after->next; |
1385 | if ( after == point ) |
1386 | goto Skip; |
1387 | |
1388 | } while ( after->org_v == point->org_v ); |
1389 | } |
1390 | |
1391 | if ( before->org_v < point->org_v && |
1392 | after->org_v > point->org_v ) |
1393 | { |
1394 | psh_point_set_positive( point ); |
1395 | } |
1396 | else if ( before->org_v > point->org_v && |
1397 | after->org_v < point->org_v ) |
1398 | { |
1399 | psh_point_set_negative( point ); |
1400 | } |
1401 | |
1402 | Skip: |
1403 | ; |
1404 | } |
1405 | } |
1406 | |
1407 | |
1408 | /* the min and max are based on contour orientation and fill rule */ |
1409 | static void |
1410 | psh_hint_table_find_strong_points( PSH_Hint_Table table, |
1411 | PSH_Point point, |
1412 | FT_UInt count, |
1413 | FT_Int threshold, |
1414 | PSH_Dir major_dir ) |
1415 | { |
1416 | PSH_Hint* sort = table->sort; |
1417 | FT_UInt num_hints = table->num_hints; |
1418 | |
1419 | |
1420 | for ( ; count > 0; count--, point++ ) |
1421 | { |
1422 | PSH_Dir point_dir; |
1423 | FT_Pos org_u = point->org_u; |
1424 | |
1425 | |
1426 | if ( psh_point_is_strong( point ) ) |
1427 | continue; |
1428 | |
1429 | point_dir = |
1430 | (PSH_Dir)( ( point->dir_in | point->dir_out ) & major_dir ); |
1431 | |
1432 | if ( point_dir & ( PSH_DIR_DOWN | PSH_DIR_RIGHT ) ) |
1433 | { |
1434 | FT_UInt nn; |
1435 | |
1436 | |
1437 | for ( nn = 0; nn < num_hints; nn++ ) |
1438 | { |
1439 | PSH_Hint hint = sort[nn]; |
1440 | FT_Pos d = org_u - hint->org_pos; |
1441 | |
1442 | |
1443 | if ( d < threshold && -d < threshold ) |
1444 | { |
1445 | psh_point_set_strong( point ); |
1446 | point->flags2 |= PSH_POINT_EDGE_MIN; |
1447 | point->hint = hint; |
1448 | break; |
1449 | } |
1450 | } |
1451 | } |
1452 | else if ( point_dir & ( PSH_DIR_UP | PSH_DIR_LEFT ) ) |
1453 | { |
1454 | FT_UInt nn; |
1455 | |
1456 | |
1457 | for ( nn = 0; nn < num_hints; nn++ ) |
1458 | { |
1459 | PSH_Hint hint = sort[nn]; |
1460 | FT_Pos d = org_u - hint->org_pos - hint->org_len; |
1461 | |
1462 | |
1463 | if ( d < threshold && -d < threshold ) |
1464 | { |
1465 | psh_point_set_strong( point ); |
1466 | point->flags2 |= PSH_POINT_EDGE_MAX; |
1467 | point->hint = hint; |
1468 | break; |
1469 | } |
1470 | } |
1471 | } |
1472 | |
1473 | #if 1 |
1474 | else if ( psh_point_is_extremum( point ) ) |
1475 | { |
1476 | /* treat extrema as special cases for stem edge alignment */ |
1477 | FT_UInt nn, min_flag, max_flag; |
1478 | |
1479 | |
1480 | if ( major_dir == PSH_DIR_HORIZONTAL ) |
1481 | { |
1482 | min_flag = PSH_POINT_POSITIVE; |
1483 | max_flag = PSH_POINT_NEGATIVE; |
1484 | } |
1485 | else |
1486 | { |
1487 | min_flag = PSH_POINT_NEGATIVE; |
1488 | max_flag = PSH_POINT_POSITIVE; |
1489 | } |
1490 | |
1491 | if ( point->flags2 & min_flag ) |
1492 | { |
1493 | for ( nn = 0; nn < num_hints; nn++ ) |
1494 | { |
1495 | PSH_Hint hint = sort[nn]; |
1496 | FT_Pos d = org_u - hint->org_pos; |
1497 | |
1498 | |
1499 | if ( d < threshold && -d < threshold ) |
1500 | { |
1501 | point->flags2 |= PSH_POINT_EDGE_MIN; |
1502 | point->hint = hint; |
1503 | psh_point_set_strong( point ); |
1504 | break; |
1505 | } |
1506 | } |
1507 | } |
1508 | else if ( point->flags2 & max_flag ) |
1509 | { |
1510 | for ( nn = 0; nn < num_hints; nn++ ) |
1511 | { |
1512 | PSH_Hint hint = sort[nn]; |
1513 | FT_Pos d = org_u - hint->org_pos - hint->org_len; |
1514 | |
1515 | |
1516 | if ( d < threshold && -d < threshold ) |
1517 | { |
1518 | point->flags2 |= PSH_POINT_EDGE_MAX; |
1519 | point->hint = hint; |
1520 | psh_point_set_strong( point ); |
1521 | break; |
1522 | } |
1523 | } |
1524 | } |
1525 | |
1526 | if ( !point->hint ) |
1527 | { |
1528 | for ( nn = 0; nn < num_hints; nn++ ) |
1529 | { |
1530 | PSH_Hint hint = sort[nn]; |
1531 | |
1532 | |
1533 | if ( org_u >= hint->org_pos && |
1534 | org_u <= ADD_INT( hint->org_pos, hint->org_len ) ) |
1535 | { |
1536 | point->hint = hint; |
1537 | break; |
1538 | } |
1539 | } |
1540 | } |
1541 | } |
1542 | |
1543 | #endif /* 1 */ |
1544 | } |
1545 | } |
1546 | |
1547 | |
1548 | /* the accepted shift for strong points in fractional pixels */ |
1549 | #define PSH_STRONG_THRESHOLD 32 |
1550 | |
1551 | /* the maximum shift value in font units tuned to distinguish */ |
1552 | /* between stems and serifs in URW+ font collection */ |
1553 | #define PSH_STRONG_THRESHOLD_MAXIMUM 12 |
1554 | |
1555 | |
1556 | /* find strong points in a glyph */ |
1557 | static void |
1558 | psh_glyph_find_strong_points( PSH_Glyph glyph, |
1559 | FT_Int dimension ) |
1560 | { |
1561 | /* a point is `strong' if it is located on a stem edge and */ |
1562 | /* has an `in' or `out' tangent parallel to the hint's direction */ |
1563 | |
1564 | PSH_Hint_Table table = &glyph->hint_tables[dimension]; |
1565 | PS_Mask mask = table->hint_masks->masks; |
1566 | FT_UInt num_masks = table->hint_masks->num_masks; |
1567 | FT_UInt first = 0; |
1568 | PSH_Dir major_dir = ( dimension == 0 ) ? PSH_DIR_VERTICAL |
1569 | : PSH_DIR_HORIZONTAL; |
1570 | PSH_Dimension dim = &glyph->globals->dimension[dimension]; |
1571 | FT_Fixed scale = dim->scale_mult; |
1572 | FT_Int threshold; |
1573 | |
1574 | |
1575 | threshold = (FT_Int)FT_DivFix( PSH_STRONG_THRESHOLD, scale ); |
1576 | if ( threshold > PSH_STRONG_THRESHOLD_MAXIMUM ) |
1577 | threshold = PSH_STRONG_THRESHOLD_MAXIMUM; |
1578 | |
1579 | /* process secondary hints to `selected' points */ |
1580 | if ( num_masks > 1 && glyph->num_points > 0 ) |
1581 | { |
1582 | /* the `endchar' op can reduce the number of points */ |
1583 | first = mask->end_point > glyph->num_points |
1584 | ? glyph->num_points |
1585 | : mask->end_point; |
1586 | mask++; |
1587 | for ( ; num_masks > 1; num_masks--, mask++ ) |
1588 | { |
1589 | FT_UInt next = FT_MIN( mask->end_point, glyph->num_points ); |
1590 | |
1591 | |
1592 | if ( next > first ) |
1593 | { |
1594 | FT_UInt count = next - first; |
1595 | PSH_Point point = glyph->points + first; |
1596 | |
1597 | |
1598 | psh_hint_table_activate_mask( table, mask ); |
1599 | |
1600 | psh_hint_table_find_strong_points( table, point, count, |
1601 | threshold, major_dir ); |
1602 | } |
1603 | first = next; |
1604 | } |
1605 | } |
1606 | |
1607 | /* process primary hints for all points */ |
1608 | if ( num_masks == 1 ) |
1609 | { |
1610 | FT_UInt count = glyph->num_points; |
1611 | PSH_Point point = glyph->points; |
1612 | |
1613 | |
1614 | psh_hint_table_activate_mask( table, table->hint_masks->masks ); |
1615 | |
1616 | psh_hint_table_find_strong_points( table, point, count, |
1617 | threshold, major_dir ); |
1618 | } |
1619 | |
1620 | /* now, certain points may have been attached to a hint and */ |
1621 | /* not marked as strong; update their flags then */ |
1622 | { |
1623 | FT_UInt count = glyph->num_points; |
1624 | PSH_Point point = glyph->points; |
1625 | |
1626 | |
1627 | for ( ; count > 0; count--, point++ ) |
1628 | if ( point->hint && !psh_point_is_strong( point ) ) |
1629 | psh_point_set_strong( point ); |
1630 | } |
1631 | } |
1632 | |
1633 | |
1634 | /* find points in a glyph which are in a blue zone and have `in' or */ |
1635 | /* `out' tangents parallel to the horizontal axis */ |
1636 | static void |
1637 | psh_glyph_find_blue_points( PSH_Blues blues, |
1638 | PSH_Glyph glyph ) |
1639 | { |
1640 | PSH_Blue_Table table; |
1641 | PSH_Blue_Zone zone; |
1642 | FT_UInt glyph_count = glyph->num_points; |
1643 | FT_UInt blue_count; |
1644 | PSH_Point point = glyph->points; |
1645 | |
1646 | |
1647 | for ( ; glyph_count > 0; glyph_count--, point++ ) |
1648 | { |
1649 | FT_Pos y; |
1650 | |
1651 | |
1652 | /* check tangents */ |
1653 | if ( !( point->dir_in & PSH_DIR_HORIZONTAL ) && |
1654 | !( point->dir_out & PSH_DIR_HORIZONTAL ) ) |
1655 | continue; |
1656 | |
1657 | /* skip strong points */ |
1658 | if ( psh_point_is_strong( point ) ) |
1659 | continue; |
1660 | |
1661 | y = point->org_u; |
1662 | |
1663 | /* look up top zones */ |
1664 | table = &blues->normal_top; |
1665 | blue_count = table->count; |
1666 | zone = table->zones; |
1667 | |
1668 | for ( ; blue_count > 0; blue_count--, zone++ ) |
1669 | { |
1670 | FT_Pos delta = y - zone->org_bottom; |
1671 | |
1672 | |
1673 | if ( delta < -blues->blue_fuzz ) |
1674 | break; |
1675 | |
1676 | if ( y <= zone->org_top + blues->blue_fuzz ) |
1677 | if ( blues->no_overshoots || delta <= blues->blue_threshold ) |
1678 | { |
1679 | point->cur_u = zone->cur_bottom; |
1680 | psh_point_set_strong( point ); |
1681 | psh_point_set_fitted( point ); |
1682 | } |
1683 | } |
1684 | |
1685 | /* look up bottom zones */ |
1686 | table = &blues->normal_bottom; |
1687 | blue_count = table->count; |
1688 | zone = table->zones + blue_count - 1; |
1689 | |
1690 | for ( ; blue_count > 0; blue_count--, zone-- ) |
1691 | { |
1692 | FT_Pos delta = zone->org_top - y; |
1693 | |
1694 | |
1695 | if ( delta < -blues->blue_fuzz ) |
1696 | break; |
1697 | |
1698 | if ( y >= zone->org_bottom - blues->blue_fuzz ) |
1699 | if ( blues->no_overshoots || delta < blues->blue_threshold ) |
1700 | { |
1701 | point->cur_u = zone->cur_top; |
1702 | psh_point_set_strong( point ); |
1703 | psh_point_set_fitted( point ); |
1704 | } |
1705 | } |
1706 | } |
1707 | } |
1708 | |
1709 | |
1710 | /* interpolate strong points with the help of hinted coordinates */ |
1711 | static void |
1712 | psh_glyph_interpolate_strong_points( PSH_Glyph glyph, |
1713 | FT_Int dimension ) |
1714 | { |
1715 | PSH_Dimension dim = &glyph->globals->dimension[dimension]; |
1716 | FT_Fixed scale = dim->scale_mult; |
1717 | |
1718 | FT_UInt count = glyph->num_points; |
1719 | PSH_Point point = glyph->points; |
1720 | |
1721 | |
1722 | for ( ; count > 0; count--, point++ ) |
1723 | { |
1724 | PSH_Hint hint = point->hint; |
1725 | |
1726 | |
1727 | if ( hint ) |
1728 | { |
1729 | FT_Pos delta; |
1730 | |
1731 | |
1732 | if ( psh_point_is_edge_min( point ) ) |
1733 | point->cur_u = hint->cur_pos; |
1734 | |
1735 | else if ( psh_point_is_edge_max( point ) ) |
1736 | point->cur_u = hint->cur_pos + hint->cur_len; |
1737 | |
1738 | else |
1739 | { |
1740 | delta = point->org_u - hint->org_pos; |
1741 | |
1742 | if ( delta <= 0 ) |
1743 | point->cur_u = hint->cur_pos + FT_MulFix( delta, scale ); |
1744 | |
1745 | else if ( delta >= hint->org_len ) |
1746 | point->cur_u = hint->cur_pos + hint->cur_len + |
1747 | FT_MulFix( delta - hint->org_len, scale ); |
1748 | |
1749 | else /* hint->org_len > 0 */ |
1750 | point->cur_u = hint->cur_pos + |
1751 | FT_MulDiv( delta, hint->cur_len, |
1752 | hint->org_len ); |
1753 | } |
1754 | psh_point_set_fitted( point ); |
1755 | } |
1756 | } |
1757 | } |
1758 | |
1759 | |
1760 | #define PSH_MAX_STRONG_INTERNAL 16 |
1761 | |
1762 | static void |
1763 | psh_glyph_interpolate_normal_points( PSH_Glyph glyph, |
1764 | FT_Int dimension ) |
1765 | { |
1766 | |
1767 | #if 1 |
1768 | /* first technique: a point is strong if it is a local extremum */ |
1769 | |
1770 | PSH_Dimension dim = &glyph->globals->dimension[dimension]; |
1771 | FT_Fixed scale = dim->scale_mult; |
1772 | FT_Memory memory = glyph->memory; |
1773 | |
1774 | PSH_Point* strongs = NULL; |
1775 | PSH_Point strongs_0[PSH_MAX_STRONG_INTERNAL]; |
1776 | FT_UInt num_strongs = 0; |
1777 | |
1778 | PSH_Point points = glyph->points; |
1779 | PSH_Point points_end = points + glyph->num_points; |
1780 | PSH_Point point; |
1781 | |
1782 | |
1783 | /* first count the number of strong points */ |
1784 | for ( point = points; point < points_end; point++ ) |
1785 | { |
1786 | if ( psh_point_is_strong( point ) ) |
1787 | num_strongs++; |
1788 | } |
1789 | |
1790 | if ( num_strongs == 0 ) /* nothing to do here */ |
1791 | return; |
1792 | |
1793 | /* allocate an array to store a list of points, */ |
1794 | /* stored in increasing org_u order */ |
1795 | if ( num_strongs <= PSH_MAX_STRONG_INTERNAL ) |
1796 | strongs = strongs_0; |
1797 | else |
1798 | { |
1799 | FT_Error error; |
1800 | |
1801 | |
1802 | if ( FT_QNEW_ARRAY( strongs, num_strongs ) ) |
1803 | return; |
1804 | } |
1805 | |
1806 | num_strongs = 0; |
1807 | for ( point = points; point < points_end; point++ ) |
1808 | { |
1809 | PSH_Point* insert; |
1810 | |
1811 | |
1812 | if ( !psh_point_is_strong( point ) ) |
1813 | continue; |
1814 | |
1815 | for ( insert = strongs + num_strongs; insert > strongs; insert-- ) |
1816 | { |
1817 | if ( insert[-1]->org_u <= point->org_u ) |
1818 | break; |
1819 | |
1820 | insert[0] = insert[-1]; |
1821 | } |
1822 | insert[0] = point; |
1823 | num_strongs++; |
1824 | } |
1825 | |
1826 | /* now try to interpolate all normal points */ |
1827 | for ( point = points; point < points_end; point++ ) |
1828 | { |
1829 | if ( psh_point_is_strong( point ) ) |
1830 | continue; |
1831 | |
1832 | /* sometimes, some local extrema are smooth points */ |
1833 | if ( psh_point_is_smooth( point ) ) |
1834 | { |
1835 | if ( point->dir_in == PSH_DIR_NONE || |
1836 | point->dir_in != point->dir_out ) |
1837 | continue; |
1838 | |
1839 | if ( !psh_point_is_extremum( point ) && |
1840 | !psh_point_is_inflex( point ) ) |
1841 | continue; |
1842 | |
1843 | point->flags &= ~PSH_POINT_SMOOTH; |
1844 | } |
1845 | |
1846 | /* find best enclosing point coordinates then interpolate */ |
1847 | { |
1848 | PSH_Point before, after; |
1849 | FT_UInt nn; |
1850 | |
1851 | |
1852 | for ( nn = 0; nn < num_strongs; nn++ ) |
1853 | if ( strongs[nn]->org_u > point->org_u ) |
1854 | break; |
1855 | |
1856 | if ( nn == 0 ) /* point before the first strong point */ |
1857 | { |
1858 | after = strongs[0]; |
1859 | |
1860 | point->cur_u = after->cur_u + |
1861 | FT_MulFix( point->org_u - after->org_u, |
1862 | scale ); |
1863 | } |
1864 | else |
1865 | { |
1866 | before = strongs[nn - 1]; |
1867 | |
1868 | for ( nn = num_strongs; nn > 0; nn-- ) |
1869 | if ( strongs[nn - 1]->org_u < point->org_u ) |
1870 | break; |
1871 | |
1872 | if ( nn == num_strongs ) /* point is after last strong point */ |
1873 | { |
1874 | before = strongs[nn - 1]; |
1875 | |
1876 | point->cur_u = before->cur_u + |
1877 | FT_MulFix( point->org_u - before->org_u, |
1878 | scale ); |
1879 | } |
1880 | else |
1881 | { |
1882 | FT_Pos u; |
1883 | |
1884 | |
1885 | after = strongs[nn]; |
1886 | |
1887 | /* now interpolate point between before and after */ |
1888 | u = point->org_u; |
1889 | |
1890 | if ( u == before->org_u ) |
1891 | point->cur_u = before->cur_u; |
1892 | |
1893 | else if ( u == after->org_u ) |
1894 | point->cur_u = after->cur_u; |
1895 | |
1896 | else |
1897 | point->cur_u = before->cur_u + |
1898 | FT_MulDiv( u - before->org_u, |
1899 | after->cur_u - before->cur_u, |
1900 | after->org_u - before->org_u ); |
1901 | } |
1902 | } |
1903 | psh_point_set_fitted( point ); |
1904 | } |
1905 | } |
1906 | |
1907 | if ( strongs != strongs_0 ) |
1908 | FT_FREE( strongs ); |
1909 | |
1910 | #endif /* 1 */ |
1911 | |
1912 | } |
1913 | |
1914 | |
1915 | /* interpolate other points */ |
1916 | static void |
1917 | psh_glyph_interpolate_other_points( PSH_Glyph glyph, |
1918 | FT_Int dimension ) |
1919 | { |
1920 | PSH_Dimension dim = &glyph->globals->dimension[dimension]; |
1921 | FT_Fixed scale = dim->scale_mult; |
1922 | FT_Fixed delta = dim->scale_delta; |
1923 | PSH_Contour contour = glyph->contours; |
1924 | FT_UInt num_contours = glyph->num_contours; |
1925 | |
1926 | |
1927 | for ( ; num_contours > 0; num_contours--, contour++ ) |
1928 | { |
1929 | PSH_Point start = contour->start; |
1930 | PSH_Point first, next, point; |
1931 | FT_UInt fit_count; |
1932 | |
1933 | |
1934 | /* count the number of strong points in this contour */ |
1935 | next = start + contour->count; |
1936 | fit_count = 0; |
1937 | first = NULL; |
1938 | |
1939 | for ( point = start; point < next; point++ ) |
1940 | if ( psh_point_is_fitted( point ) ) |
1941 | { |
1942 | if ( !first ) |
1943 | first = point; |
1944 | |
1945 | fit_count++; |
1946 | } |
1947 | |
1948 | /* if there are less than 2 fitted points in the contour, we */ |
1949 | /* simply scale and eventually translate the contour points */ |
1950 | if ( fit_count < 2 ) |
1951 | { |
1952 | if ( fit_count == 1 ) |
1953 | delta = first->cur_u - FT_MulFix( first->org_u, scale ); |
1954 | |
1955 | for ( point = start; point < next; point++ ) |
1956 | if ( point != first ) |
1957 | point->cur_u = FT_MulFix( point->org_u, scale ) + delta; |
1958 | |
1959 | goto Next_Contour; |
1960 | } |
1961 | |
1962 | /* there are more than 2 strong points in this contour; we */ |
1963 | /* need to interpolate weak points between them */ |
1964 | start = first; |
1965 | do |
1966 | { |
1967 | /* skip consecutive fitted points */ |
1968 | for (;;) |
1969 | { |
1970 | next = first->next; |
1971 | if ( next == start ) |
1972 | goto Next_Contour; |
1973 | |
1974 | if ( !psh_point_is_fitted( next ) ) |
1975 | break; |
1976 | |
1977 | first = next; |
1978 | } |
1979 | |
1980 | /* find next fitted point after unfitted one */ |
1981 | for (;;) |
1982 | { |
1983 | next = next->next; |
1984 | if ( psh_point_is_fitted( next ) ) |
1985 | break; |
1986 | } |
1987 | |
1988 | /* now interpolate between them */ |
1989 | { |
1990 | FT_Pos org_a, org_ab, cur_a, cur_ab; |
1991 | FT_Pos org_c, org_ac, cur_c; |
1992 | FT_Fixed scale_ab; |
1993 | |
1994 | |
1995 | if ( first->org_u <= next->org_u ) |
1996 | { |
1997 | org_a = first->org_u; |
1998 | cur_a = first->cur_u; |
1999 | org_ab = next->org_u - org_a; |
2000 | cur_ab = next->cur_u - cur_a; |
2001 | } |
2002 | else |
2003 | { |
2004 | org_a = next->org_u; |
2005 | cur_a = next->cur_u; |
2006 | org_ab = first->org_u - org_a; |
2007 | cur_ab = first->cur_u - cur_a; |
2008 | } |
2009 | |
2010 | scale_ab = 0x10000L; |
2011 | if ( org_ab > 0 ) |
2012 | scale_ab = FT_DivFix( cur_ab, org_ab ); |
2013 | |
2014 | point = first->next; |
2015 | do |
2016 | { |
2017 | org_c = point->org_u; |
2018 | org_ac = org_c - org_a; |
2019 | |
2020 | if ( org_ac <= 0 ) |
2021 | { |
2022 | /* on the left of the interpolation zone */ |
2023 | cur_c = cur_a + FT_MulFix( org_ac, scale ); |
2024 | } |
2025 | else if ( org_ac >= org_ab ) |
2026 | { |
2027 | /* on the right on the interpolation zone */ |
2028 | cur_c = cur_a + cur_ab + FT_MulFix( org_ac - org_ab, scale ); |
2029 | } |
2030 | else |
2031 | { |
2032 | /* within the interpolation zone */ |
2033 | cur_c = cur_a + FT_MulFix( org_ac, scale_ab ); |
2034 | } |
2035 | |
2036 | point->cur_u = cur_c; |
2037 | |
2038 | point = point->next; |
2039 | |
2040 | } while ( point != next ); |
2041 | } |
2042 | |
2043 | /* keep going until all points in the contours have been processed */ |
2044 | first = next; |
2045 | |
2046 | } while ( first != start ); |
2047 | |
2048 | Next_Contour: |
2049 | ; |
2050 | } |
2051 | } |
2052 | |
2053 | |
2054 | /*************************************************************************/ |
2055 | /*************************************************************************/ |
2056 | /***** *****/ |
2057 | /***** HIGH-LEVEL INTERFACE *****/ |
2058 | /***** *****/ |
2059 | /*************************************************************************/ |
2060 | /*************************************************************************/ |
2061 | |
2062 | FT_Error |
2063 | ps_hints_apply( PS_Hints ps_hints, |
2064 | FT_Outline* outline, |
2065 | PSH_Globals globals, |
2066 | FT_Render_Mode hint_mode ) |
2067 | { |
2068 | PSH_GlyphRec glyphrec; |
2069 | PSH_Glyph glyph = &glyphrec; |
2070 | FT_Error error; |
2071 | #ifdef DEBUG_HINTER |
2072 | FT_Memory memory; |
2073 | #endif |
2074 | FT_Int dimension; |
2075 | |
2076 | |
2077 | /* something to do? */ |
2078 | if ( outline->n_points == 0 || outline->n_contours == 0 ) |
2079 | return FT_Err_Ok; |
2080 | |
2081 | #ifdef DEBUG_HINTER |
2082 | |
2083 | memory = globals->memory; |
2084 | |
2085 | if ( ps_debug_glyph ) |
2086 | { |
2087 | psh_glyph_done( ps_debug_glyph ); |
2088 | FT_FREE( ps_debug_glyph ); |
2089 | } |
2090 | |
2091 | if ( FT_NEW( glyph ) ) |
2092 | return error; |
2093 | |
2094 | ps_debug_glyph = glyph; |
2095 | |
2096 | #endif /* DEBUG_HINTER */ |
2097 | |
2098 | error = psh_glyph_init( glyph, outline, ps_hints, globals ); |
2099 | if ( error ) |
2100 | goto Exit; |
2101 | |
2102 | /* try to optimize the y_scale so that the top of non-capital letters |
2103 | * is aligned on a pixel boundary whenever possible |
2104 | */ |
2105 | { |
2106 | PSH_Dimension dim_x = &glyph->globals->dimension[0]; |
2107 | PSH_Dimension dim_y = &glyph->globals->dimension[1]; |
2108 | |
2109 | FT_Fixed x_scale = dim_x->scale_mult; |
2110 | FT_Fixed y_scale = dim_y->scale_mult; |
2111 | |
2112 | FT_Fixed old_x_scale = x_scale; |
2113 | FT_Fixed old_y_scale = y_scale; |
2114 | |
2115 | FT_Fixed scaled = 0; |
2116 | FT_Fixed fitted = 0; |
2117 | |
2118 | FT_Bool rescale = FALSE; |
2119 | |
2120 | |
2121 | if ( globals->blues.normal_top.count ) |
2122 | { |
2123 | scaled = FT_MulFix( globals->blues.normal_top.zones->org_ref, y_scale ); |
2124 | fitted = FT_PIX_ROUND( scaled ); |
2125 | } |
2126 | |
2127 | if ( fitted != 0 && scaled != fitted ) |
2128 | { |
2129 | rescale = TRUE; |
2130 | |
2131 | y_scale = FT_MulDiv( y_scale, fitted, scaled ); |
2132 | |
2133 | if ( fitted < scaled ) |
2134 | x_scale -= x_scale / 50; |
2135 | |
2136 | psh_globals_set_scale( glyph->globals, x_scale, y_scale, 0, 0 ); |
2137 | } |
2138 | |
2139 | glyph->do_horz_hints = 1; |
2140 | glyph->do_vert_hints = 1; |
2141 | |
2142 | glyph->do_horz_snapping = FT_BOOL( hint_mode == FT_RENDER_MODE_MONO || |
2143 | hint_mode == FT_RENDER_MODE_LCD ); |
2144 | |
2145 | glyph->do_vert_snapping = FT_BOOL( hint_mode == FT_RENDER_MODE_MONO || |
2146 | hint_mode == FT_RENDER_MODE_LCD_V ); |
2147 | |
2148 | glyph->do_stem_adjust = FT_BOOL( hint_mode != FT_RENDER_MODE_LIGHT ); |
2149 | |
2150 | for ( dimension = 0; dimension < 2; dimension++ ) |
2151 | { |
2152 | /* load outline coordinates into glyph */ |
2153 | psh_glyph_load_points( glyph, dimension ); |
2154 | |
2155 | /* compute local extrema */ |
2156 | psh_glyph_compute_extrema( glyph ); |
2157 | |
2158 | /* compute aligned stem/hints positions */ |
2159 | psh_hint_table_align_hints( &glyph->hint_tables[dimension], |
2160 | glyph->globals, |
2161 | dimension, |
2162 | glyph ); |
2163 | |
2164 | /* find strong points, align them, then interpolate others */ |
2165 | psh_glyph_find_strong_points( glyph, dimension ); |
2166 | if ( dimension == 1 ) |
2167 | psh_glyph_find_blue_points( &globals->blues, glyph ); |
2168 | psh_glyph_interpolate_strong_points( glyph, dimension ); |
2169 | psh_glyph_interpolate_normal_points( glyph, dimension ); |
2170 | psh_glyph_interpolate_other_points( glyph, dimension ); |
2171 | |
2172 | /* save hinted coordinates back to outline */ |
2173 | psh_glyph_save_points( glyph, dimension ); |
2174 | |
2175 | if ( rescale ) |
2176 | psh_globals_set_scale( glyph->globals, |
2177 | old_x_scale, old_y_scale, 0, 0 ); |
2178 | } |
2179 | } |
2180 | |
2181 | Exit: |
2182 | |
2183 | #ifndef DEBUG_HINTER |
2184 | psh_glyph_done( glyph ); |
2185 | #endif |
2186 | |
2187 | return error; |
2188 | } |
2189 | |
2190 | |
2191 | /* END */ |
2192 | |