1/****************************************************************************
2 *
3 * aflatin.c
4 *
5 * Auto-fitter hinting routines for latin writing system (body).
6 *
7 * Copyright (C) 2003-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/ftadvanc.h>
20#include <freetype/internal/ftdebug.h>
21
22#include "afglobal.h"
23#include "aflatin.h"
24#include "aferrors.h"
25
26
27 /**************************************************************************
28 *
29 * The macro FT_COMPONENT is used in trace mode. It is an implicit
30 * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
31 * messages during execution.
32 */
33#undef FT_COMPONENT
34#define FT_COMPONENT aflatin
35
36
37 /* needed for computation of round vs. flat segments */
38#define FLAT_THRESHOLD( x ) ( x / 14 )
39
40
41 /*************************************************************************/
42 /*************************************************************************/
43 /***** *****/
44 /***** L A T I N G L O B A L M E T R I C S *****/
45 /***** *****/
46 /*************************************************************************/
47 /*************************************************************************/
48
49
50 /* Find segments and links, compute all stem widths, and initialize */
51 /* standard width and height for the glyph with given charcode. */
52
53 FT_LOCAL_DEF( void )
54 af_latin_metrics_init_widths( AF_LatinMetrics metrics,
55 FT_Face face )
56 {
57 /* scan the array of segments in each direction */
58 AF_GlyphHintsRec hints[1];
59
60
61 FT_TRACE5(( "\n" ));
62 FT_TRACE5(( "latin standard widths computation (style `%s')\n",
63 af_style_names[metrics->root.style_class->style] ));
64 FT_TRACE5(( "=====================================================\n" ));
65 FT_TRACE5(( "\n" ));
66
67 af_glyph_hints_init( hints, face->memory );
68
69 metrics->axis[AF_DIMENSION_HORZ].width_count = 0;
70 metrics->axis[AF_DIMENSION_VERT].width_count = 0;
71
72 {
73 FT_Error error;
74 FT_ULong glyph_index;
75 int dim;
76 AF_LatinMetricsRec dummy[1];
77 AF_Scaler scaler = &dummy->root.scaler;
78
79 AF_StyleClass style_class = metrics->root.style_class;
80 AF_ScriptClass script_class = af_script_classes[style_class->script];
81
82 /* If HarfBuzz is not available, we need a pointer to a single */
83 /* unsigned long value. */
84#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
85 void* shaper_buf;
86#else
87 FT_ULong shaper_buf_;
88 void* shaper_buf = &shaper_buf_;
89#endif
90
91 const char* p;
92
93#ifdef FT_DEBUG_LEVEL_TRACE
94 FT_ULong ch = 0;
95#endif
96
97
98 p = script_class->standard_charstring;
99
100#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
101 shaper_buf = af_shaper_buf_create( face );
102#endif
103 /*
104 * We check a list of standard characters to catch features like
105 * `c2sc' (small caps from caps) that don't contain lowercase letters
106 * by definition, or other features that mainly operate on numerals.
107 * The first match wins.
108 */
109
110 glyph_index = 0;
111 while ( *p )
112 {
113 unsigned int num_idx;
114
115#ifdef FT_DEBUG_LEVEL_TRACE
116 const char* p_old;
117#endif
118
119
120 while ( *p == ' ' )
121 p++;
122
123#ifdef FT_DEBUG_LEVEL_TRACE
124 p_old = p;
125 GET_UTF8_CHAR( ch, p_old );
126#endif
127
128 /* reject input that maps to more than a single glyph */
129 p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
130 if ( num_idx > 1 )
131 continue;
132
133 /* otherwise exit loop if we have a result */
134 glyph_index = af_shaper_get_elem( &metrics->root,
135 shaper_buf,
136 0,
137 NULL,
138 NULL );
139 if ( glyph_index )
140 break;
141 }
142
143 af_shaper_buf_destroy( face, shaper_buf );
144
145 if ( !glyph_index )
146 {
147 FT_TRACE5(( "standard character missing;"
148 " using fallback stem widths\n" ));
149 goto Exit;
150 }
151
152 FT_TRACE5(( "standard character: U+%04lX (glyph index %ld)\n",
153 ch, glyph_index ));
154
155 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
156 if ( error || face->glyph->outline.n_points <= 0 )
157 goto Exit;
158
159 FT_ZERO( dummy );
160
161 dummy->units_per_em = metrics->units_per_em;
162
163 scaler->x_scale = 0x10000L;
164 scaler->y_scale = 0x10000L;
165 scaler->x_delta = 0;
166 scaler->y_delta = 0;
167
168 scaler->face = face;
169 scaler->render_mode = FT_RENDER_MODE_NORMAL;
170 scaler->flags = 0;
171
172 af_glyph_hints_rescale( hints, (AF_StyleMetrics)dummy );
173
174 error = af_glyph_hints_reload( hints, &face->glyph->outline );
175 if ( error )
176 goto Exit;
177
178 for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
179 {
180 AF_LatinAxis axis = &metrics->axis[dim];
181 AF_AxisHints axhints = &hints->axis[dim];
182 AF_Segment seg, limit, link;
183 FT_UInt num_widths = 0;
184
185
186 error = af_latin_hints_compute_segments( hints,
187 (AF_Dimension)dim );
188 if ( error )
189 goto Exit;
190
191 /*
192 * We assume that the glyphs selected for the stem width
193 * computation are `featureless' enough so that the linking
194 * algorithm works fine without adjustments of its scoring
195 * function.
196 */
197 af_latin_hints_link_segments( hints,
198 0,
199 NULL,
200 (AF_Dimension)dim );
201
202 seg = axhints->segments;
203 limit = FT_OFFSET( seg, axhints->num_segments );
204
205 for ( ; seg < limit; seg++ )
206 {
207 link = seg->link;
208
209 /* we only consider stem segments there! */
210 if ( link && link->link == seg && link > seg )
211 {
212 FT_Pos dist;
213
214
215 dist = seg->pos - link->pos;
216 if ( dist < 0 )
217 dist = -dist;
218
219 if ( num_widths < AF_LATIN_MAX_WIDTHS )
220 axis->widths[num_widths++].org = dist;
221 }
222 }
223
224 /* this also replaces multiple almost identical stem widths */
225 /* with a single one (the value 100 is heuristic) */
226 af_sort_and_quantize_widths( &num_widths, axis->widths,
227 dummy->units_per_em / 100 );
228 axis->width_count = num_widths;
229 }
230
231 Exit:
232 for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
233 {
234 AF_LatinAxis axis = &metrics->axis[dim];
235 FT_Pos stdw;
236
237
238 stdw = ( axis->width_count > 0 ) ? axis->widths[0].org
239 : AF_LATIN_CONSTANT( metrics, 50 );
240
241 /* let's try 20% of the smallest width */
242 axis->edge_distance_threshold = stdw / 5;
243 axis->standard_width = stdw;
244 axis->extra_light = 0;
245
246#ifdef FT_DEBUG_LEVEL_TRACE
247 {
248 FT_UInt i;
249
250
251 FT_TRACE5(( "%s widths:\n",
252 dim == AF_DIMENSION_VERT ? "horizontal"
253 : "vertical" ));
254
255 FT_TRACE5(( " %ld (standard)", axis->standard_width ));
256 for ( i = 1; i < axis->width_count; i++ )
257 FT_TRACE5(( " %ld", axis->widths[i].org ));
258
259 FT_TRACE5(( "\n" ));
260 }
261#endif
262 }
263 }
264
265 FT_TRACE5(( "\n" ));
266
267 af_glyph_hints_done( hints );
268 }
269
270
271 static void
272 af_latin_sort_blue( FT_UInt count,
273 AF_LatinBlue* table )
274 {
275 FT_UInt i, j;
276 AF_LatinBlue swap;
277
278
279 /* we sort from bottom to top */
280 for ( i = 1; i < count; i++ )
281 {
282 for ( j = i; j > 0; j-- )
283 {
284 FT_Pos a, b;
285
286
287 if ( table[j - 1]->flags & ( AF_LATIN_BLUE_TOP |
288 AF_LATIN_BLUE_SUB_TOP ) )
289 a = table[j - 1]->ref.org;
290 else
291 a = table[j - 1]->shoot.org;
292
293 if ( table[j]->flags & ( AF_LATIN_BLUE_TOP |
294 AF_LATIN_BLUE_SUB_TOP ) )
295 b = table[j]->ref.org;
296 else
297 b = table[j]->shoot.org;
298
299 if ( b >= a )
300 break;
301
302 swap = table[j];
303 table[j] = table[j - 1];
304 table[j - 1] = swap;
305 }
306 }
307 }
308
309
310 /* Find all blue zones. Flat segments give the reference points, */
311 /* round segments the overshoot positions. */
312
313 static int
314 af_latin_metrics_init_blues( AF_LatinMetrics metrics,
315 FT_Face face )
316 {
317 FT_Pos flats [AF_BLUE_STRING_MAX_LEN];
318 FT_Pos rounds[AF_BLUE_STRING_MAX_LEN];
319
320 FT_UInt num_flats;
321 FT_UInt num_rounds;
322
323 AF_LatinBlue blue;
324 FT_Error error;
325 AF_LatinAxis axis = &metrics->axis[AF_DIMENSION_VERT];
326 FT_Outline outline;
327
328 AF_StyleClass sc = metrics->root.style_class;
329
330 AF_Blue_Stringset bss = sc->blue_stringset;
331 const AF_Blue_StringRec* bs = &af_blue_stringsets[bss];
332
333 FT_Pos flat_threshold = FLAT_THRESHOLD( metrics->units_per_em );
334
335 /* If HarfBuzz is not available, we need a pointer to a single */
336 /* unsigned long value. */
337#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
338 void* shaper_buf;
339#else
340 FT_ULong shaper_buf_;
341 void* shaper_buf = &shaper_buf_;
342#endif
343
344
345 /* we walk over the blue character strings as specified in the */
346 /* style's entry in the `af_blue_stringset' array */
347
348 FT_TRACE5(( "latin blue zones computation\n" ));
349 FT_TRACE5(( "============================\n" ));
350 FT_TRACE5(( "\n" ));
351
352#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
353 shaper_buf = af_shaper_buf_create( face );
354#endif
355
356 for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
357 {
358 const char* p = &af_blue_strings[bs->string];
359 FT_Pos* blue_ref;
360 FT_Pos* blue_shoot;
361 FT_Pos ascender;
362 FT_Pos descender;
363
364
365#ifdef FT_DEBUG_LEVEL_TRACE
366 {
367 FT_Bool have_flag = 0;
368
369
370 FT_TRACE5(( "blue zone %d", axis->blue_count ));
371
372 if ( bs->properties )
373 {
374 FT_TRACE5(( " (" ));
375
376 if ( AF_LATIN_IS_TOP_BLUE( bs ) )
377 {
378 FT_TRACE5(( "top" ));
379 have_flag = 1;
380 }
381 else if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
382 {
383 FT_TRACE5(( "sub top" ));
384 have_flag = 1;
385 }
386
387 if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
388 {
389 if ( have_flag )
390 FT_TRACE5(( ", " ));
391 FT_TRACE5(( "neutral" ));
392 have_flag = 1;
393 }
394
395 if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) )
396 {
397 if ( have_flag )
398 FT_TRACE5(( ", " ));
399 FT_TRACE5(( "small top" ));
400 have_flag = 1;
401 }
402
403 if ( AF_LATIN_IS_LONG_BLUE( bs ) )
404 {
405 if ( have_flag )
406 FT_TRACE5(( ", " ));
407 FT_TRACE5(( "long" ));
408 }
409
410 FT_TRACE5(( ")" ));
411 }
412
413 FT_TRACE5(( ":\n" ));
414 }
415#endif /* FT_DEBUG_LEVEL_TRACE */
416
417 num_flats = 0;
418 num_rounds = 0;
419 ascender = 0;
420 descender = 0;
421
422 while ( *p )
423 {
424 FT_ULong glyph_index;
425 FT_Long y_offset;
426 FT_Int best_point, best_contour_first, best_contour_last;
427 FT_Vector* points;
428
429 FT_Pos best_y_extremum; /* same as points.y */
430 FT_Bool best_round = 0;
431
432 unsigned int i, num_idx;
433
434#ifdef FT_DEBUG_LEVEL_TRACE
435 const char* p_old;
436 FT_ULong ch;
437#endif
438
439
440 while ( *p == ' ' )
441 p++;
442
443#ifdef FT_DEBUG_LEVEL_TRACE
444 p_old = p;
445 GET_UTF8_CHAR( ch, p_old );
446#endif
447
448 p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
449
450 if ( !num_idx )
451 {
452 FT_TRACE5(( " U+%04lX unavailable\n", ch ));
453 continue;
454 }
455
456 if ( AF_LATIN_IS_TOP_BLUE( bs ) )
457 best_y_extremum = FT_INT_MIN;
458 else
459 best_y_extremum = FT_INT_MAX;
460
461 /* iterate over all glyph elements of the character cluster */
462 /* and get the data of the `biggest' one */
463 for ( i = 0; i < num_idx; i++ )
464 {
465 FT_Pos best_y;
466 FT_Bool round = 0;
467
468
469 /* load the character in the face -- skip unknown or empty ones */
470 glyph_index = af_shaper_get_elem( &metrics->root,
471 shaper_buf,
472 i,
473 NULL,
474 &y_offset );
475 if ( glyph_index == 0 )
476 {
477 FT_TRACE5(( " U+%04lX unavailable\n", ch ));
478 continue;
479 }
480
481 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
482 outline = face->glyph->outline;
483 /* reject glyphs that don't produce any rendering */
484 if ( error || outline.n_points <= 2 )
485 {
486#ifdef FT_DEBUG_LEVEL_TRACE
487 if ( num_idx == 1 )
488 FT_TRACE5(( " U+%04lX contains no (usable) outlines\n", ch ));
489 else
490 FT_TRACE5(( " component %d of cluster starting with U+%04lX"
491 " contains no (usable) outlines\n", i, ch ));
492#endif
493 continue;
494 }
495
496 /* now compute min or max point indices and coordinates */
497 points = outline.points;
498 best_point = -1;
499 best_contour_first = -1;
500 best_contour_last = -1;
501 best_y = 0; /* make compiler happy */
502
503 {
504 FT_Int nn;
505 FT_Int pp, first, last;
506
507
508 last = -1;
509 for ( nn = 0; nn < outline.n_contours; nn++ )
510 {
511 first = last + 1;
512 last = outline.contours[nn];
513
514 /* Avoid single-point contours since they are never */
515 /* rasterized. In some fonts, they correspond to mark */
516 /* attachment points that are way outside of the glyph's */
517 /* real outline. */
518 if ( last <= first )
519 continue;
520
521 if ( AF_LATIN_IS_TOP_BLUE( bs ) ||
522 AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
523 {
524 for ( pp = first; pp <= last; pp++ )
525 {
526 if ( best_point < 0 || points[pp].y > best_y )
527 {
528 best_point = pp;
529 best_y = points[pp].y;
530 ascender = FT_MAX( ascender, best_y + y_offset );
531 }
532 else
533 descender = FT_MIN( descender, points[pp].y + y_offset );
534 }
535 }
536 else
537 {
538 for ( pp = first; pp <= last; pp++ )
539 {
540 if ( best_point < 0 || points[pp].y < best_y )
541 {
542 best_point = pp;
543 best_y = points[pp].y;
544 descender = FT_MIN( descender, best_y + y_offset );
545 }
546 else
547 ascender = FT_MAX( ascender, points[pp].y + y_offset );
548 }
549 }
550
551 if ( best_point > best_contour_last )
552 {
553 best_contour_first = first;
554 best_contour_last = last;
555 }
556 }
557 }
558
559 /* now check whether the point belongs to a straight or round */
560 /* segment; we first need to find in which contour the extremum */
561 /* lies, then inspect its previous and next points */
562 if ( best_point >= 0 )
563 {
564 FT_Pos best_x = points[best_point].x;
565 FT_Int prev, next;
566 FT_Int best_segment_first, best_segment_last;
567 FT_Int best_on_point_first, best_on_point_last;
568 FT_Pos dist;
569
570
571 best_segment_first = best_point;
572 best_segment_last = best_point;
573
574 if ( FT_CURVE_TAG( outline.tags[best_point] ) == FT_CURVE_TAG_ON )
575 {
576 best_on_point_first = best_point;
577 best_on_point_last = best_point;
578 }
579 else
580 {
581 best_on_point_first = -1;
582 best_on_point_last = -1;
583 }
584
585 /* look for the previous and next points on the contour */
586 /* that are not on the same Y coordinate, then threshold */
587 /* the `closeness'... */
588 prev = best_point;
589 next = prev;
590
591 do
592 {
593 if ( prev > best_contour_first )
594 prev--;
595 else
596 prev = best_contour_last;
597
598 dist = FT_ABS( points[prev].y - best_y );
599 /* accept a small distance or a small angle (both values are */
600 /* heuristic; value 20 corresponds to approx. 2.9 degrees) */
601 if ( dist > 5 )
602 if ( FT_ABS( points[prev].x - best_x ) <= 20 * dist )
603 break;
604
605 best_segment_first = prev;
606
607 if ( FT_CURVE_TAG( outline.tags[prev] ) == FT_CURVE_TAG_ON )
608 {
609 best_on_point_first = prev;
610 if ( best_on_point_last < 0 )
611 best_on_point_last = prev;
612 }
613
614 } while ( prev != best_point );
615
616 do
617 {
618 if ( next < best_contour_last )
619 next++;
620 else
621 next = best_contour_first;
622
623 dist = FT_ABS( points[next].y - best_y );
624 if ( dist > 5 )
625 if ( FT_ABS( points[next].x - best_x ) <= 20 * dist )
626 break;
627
628 best_segment_last = next;
629
630 if ( FT_CURVE_TAG( outline.tags[next] ) == FT_CURVE_TAG_ON )
631 {
632 best_on_point_last = next;
633 if ( best_on_point_first < 0 )
634 best_on_point_first = next;
635 }
636
637 } while ( next != best_point );
638
639 if ( AF_LATIN_IS_LONG_BLUE( bs ) )
640 {
641 /* If this flag is set, we have an additional constraint to */
642 /* get the blue zone distance: Find a segment of the topmost */
643 /* (or bottommost) contour that is longer than a heuristic */
644 /* threshold. This ensures that small bumps in the outline */
645 /* are ignored (for example, the `vertical serifs' found in */
646 /* many Hebrew glyph designs). */
647
648 /* If this segment is long enough, we are done. Otherwise, */
649 /* search the segment next to the extremum that is long */
650 /* enough, has the same direction, and a not too large */
651 /* vertical distance from the extremum. Note that the */
652 /* algorithm doesn't check whether the found segment is */
653 /* actually the one (vertically) nearest to the extremum. */
654
655 /* heuristic threshold value */
656 FT_Pos length_threshold = metrics->units_per_em / 25;
657
658
659 dist = FT_ABS( points[best_segment_last].x -
660 points[best_segment_first].x );
661
662 if ( dist < length_threshold &&
663 best_segment_last - best_segment_first + 2 <=
664 best_contour_last - best_contour_first )
665 {
666 /* heuristic threshold value */
667 FT_Pos height_threshold = metrics->units_per_em / 4;
668
669 FT_Int first;
670 FT_Int last;
671 FT_Bool hit;
672
673 /* we intentionally declare these two variables */
674 /* outside of the loop since various compilers emit */
675 /* incorrect warning messages otherwise, talking about */
676 /* `possibly uninitialized variables' */
677 FT_Int p_first = 0; /* make compiler happy */
678 FT_Int p_last = 0;
679
680 FT_Bool left2right;
681
682
683 /* compute direction */
684 prev = best_point;
685
686 do
687 {
688 if ( prev > best_contour_first )
689 prev--;
690 else
691 prev = best_contour_last;
692
693 if ( points[prev].x != best_x )
694 break;
695
696 } while ( prev != best_point );
697
698 /* skip glyph for the degenerate case */
699 if ( prev == best_point )
700 continue;
701
702 left2right = FT_BOOL( points[prev].x < points[best_point].x );
703
704 first = best_segment_last;
705 last = first;
706 hit = 0;
707
708 do
709 {
710 FT_Bool l2r;
711 FT_Pos d;
712
713
714 if ( !hit )
715 {
716 /* no hit; adjust first point */
717 first = last;
718
719 /* also adjust first and last on point */
720 if ( FT_CURVE_TAG( outline.tags[first] ) ==
721 FT_CURVE_TAG_ON )
722 {
723 p_first = first;
724 p_last = first;
725 }
726 else
727 {
728 p_first = -1;
729 p_last = -1;
730 }
731
732 hit = 1;
733 }
734
735 if ( last < best_contour_last )
736 last++;
737 else
738 last = best_contour_first;
739
740 if ( FT_ABS( best_y - points[first].y ) > height_threshold )
741 {
742 /* vertical distance too large */
743 hit = 0;
744 continue;
745 }
746
747 /* same test as above */
748 dist = FT_ABS( points[last].y - points[first].y );
749 if ( dist > 5 )
750 if ( FT_ABS( points[last].x - points[first].x ) <=
751 20 * dist )
752 {
753 hit = 0;
754 continue;
755 }
756
757 if ( FT_CURVE_TAG( outline.tags[last] ) == FT_CURVE_TAG_ON )
758 {
759 p_last = last;
760 if ( p_first < 0 )
761 p_first = last;
762 }
763
764 l2r = FT_BOOL( points[first].x < points[last].x );
765 d = FT_ABS( points[last].x - points[first].x );
766
767 if ( l2r == left2right &&
768 d >= length_threshold )
769 {
770 /* all constraints are met; update segment after */
771 /* finding its end */
772 do
773 {
774 if ( last < best_contour_last )
775 last++;
776 else
777 last = best_contour_first;
778
779 d = FT_ABS( points[last].y - points[first].y );
780 if ( d > 5 )
781 if ( FT_ABS( points[next].x - points[first].x ) <=
782 20 * dist )
783 {
784 if ( last > best_contour_first )
785 last--;
786 else
787 last = best_contour_last;
788 break;
789 }
790
791 p_last = last;
792
793 if ( FT_CURVE_TAG( outline.tags[last] ) ==
794 FT_CURVE_TAG_ON )
795 {
796 p_last = last;
797 if ( p_first < 0 )
798 p_first = last;
799 }
800
801 } while ( last != best_segment_first );
802
803 best_y = points[first].y;
804
805 best_segment_first = first;
806 best_segment_last = last;
807
808 best_on_point_first = p_first;
809 best_on_point_last = p_last;
810
811 break;
812 }
813
814 } while ( last != best_segment_first );
815 }
816 }
817
818 /* for computing blue zones, we add the y offset as returned */
819 /* by the currently used OpenType feature -- for example, */
820 /* superscript glyphs might be identical to subscript glyphs */
821 /* with a vertical shift */
822 best_y += y_offset;
823
824#ifdef FT_DEBUG_LEVEL_TRACE
825 if ( num_idx == 1 )
826 FT_TRACE5(( " U+%04lX: best_y = %5ld", ch, best_y ));
827 else
828 FT_TRACE5(( " component %d of cluster starting with U+%04lX:"
829 " best_y = %5ld", i, ch, best_y ));
830#endif
831
832 /* now set the `round' flag depending on the segment's kind: */
833 /* */
834 /* - if the horizontal distance between the first and last */
835 /* `on' point is larger than a heuristic threshold */
836 /* we have a flat segment */
837 /* - if either the first or the last point of the segment is */
838 /* an `off' point, the segment is round, otherwise it is */
839 /* flat */
840 if ( best_on_point_first >= 0 &&
841 best_on_point_last >= 0 &&
842 ( FT_ABS( points[best_on_point_last].x -
843 points[best_on_point_first].x ) ) >
844 flat_threshold )
845 round = 0;
846 else
847 round = FT_BOOL(
848 FT_CURVE_TAG( outline.tags[best_segment_first] ) !=
849 FT_CURVE_TAG_ON ||
850 FT_CURVE_TAG( outline.tags[best_segment_last] ) !=
851 FT_CURVE_TAG_ON );
852
853 if ( round && AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
854 {
855 /* only use flat segments for a neutral blue zone */
856 FT_TRACE5(( " (round, skipped)\n" ));
857 continue;
858 }
859
860 FT_TRACE5(( " (%s)\n", round ? "round" : "flat" ));
861 }
862
863 if ( AF_LATIN_IS_TOP_BLUE( bs ) )
864 {
865 if ( best_y > best_y_extremum )
866 {
867 best_y_extremum = best_y;
868 best_round = round;
869 }
870 }
871 else
872 {
873 if ( best_y < best_y_extremum )
874 {
875 best_y_extremum = best_y;
876 best_round = round;
877 }
878 }
879
880 } /* end for loop */
881
882 if ( !( best_y_extremum == FT_INT_MIN ||
883 best_y_extremum == FT_INT_MAX ) )
884 {
885 if ( best_round )
886 rounds[num_rounds++] = best_y_extremum;
887 else
888 flats[num_flats++] = best_y_extremum;
889 }
890
891 } /* end while loop */
892
893 if ( num_flats == 0 && num_rounds == 0 )
894 {
895 /*
896 * we couldn't find a single glyph to compute this blue zone,
897 * we will simply ignore it then
898 */
899 FT_TRACE5(( " empty\n" ));
900 continue;
901 }
902
903 /* we have computed the contents of the `rounds' and `flats' tables, */
904 /* now determine the reference and overshoot position of the blue -- */
905 /* we simply take the median value after a simple sort */
906 af_sort_pos( num_rounds, rounds );
907 af_sort_pos( num_flats, flats );
908
909 blue = &axis->blues[axis->blue_count];
910 blue_ref = &blue->ref.org;
911 blue_shoot = &blue->shoot.org;
912
913 axis->blue_count++;
914
915 if ( num_flats == 0 )
916 {
917 *blue_ref =
918 *blue_shoot = rounds[num_rounds / 2];
919 }
920 else if ( num_rounds == 0 )
921 {
922 *blue_ref =
923 *blue_shoot = flats[num_flats / 2];
924 }
925 else
926 {
927 *blue_ref = flats [num_flats / 2];
928 *blue_shoot = rounds[num_rounds / 2];
929 }
930
931 /* there are sometimes problems: if the overshoot position of top */
932 /* zones is under its reference position, or the opposite for bottom */
933 /* zones. We must thus check everything there and correct the errors */
934 if ( *blue_shoot != *blue_ref )
935 {
936 FT_Pos ref = *blue_ref;
937 FT_Pos shoot = *blue_shoot;
938 FT_Bool over_ref = FT_BOOL( shoot > ref );
939
940
941 if ( ( AF_LATIN_IS_TOP_BLUE( bs ) ||
942 AF_LATIN_IS_SUB_TOP_BLUE( bs) ) ^ over_ref )
943 {
944 *blue_ref =
945 *blue_shoot = ( shoot + ref ) / 2;
946
947 FT_TRACE5(( " [overshoot smaller than reference,"
948 " taking mean value]\n" ));
949 }
950 }
951
952 blue->ascender = ascender;
953 blue->descender = descender;
954
955 blue->flags = 0;
956 if ( AF_LATIN_IS_TOP_BLUE( bs ) )
957 blue->flags |= AF_LATIN_BLUE_TOP;
958 if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
959 blue->flags |= AF_LATIN_BLUE_SUB_TOP;
960 if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
961 blue->flags |= AF_LATIN_BLUE_NEUTRAL;
962
963 /*
964 * The following flag is used later to adjust the y and x scales
965 * in order to optimize the pixel grid alignment of the top of small
966 * letters.
967 */
968 if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) )
969 blue->flags |= AF_LATIN_BLUE_ADJUSTMENT;
970
971 FT_TRACE5(( " -> reference = %ld\n", *blue_ref ));
972 FT_TRACE5(( " overshoot = %ld\n", *blue_shoot ));
973
974 } /* end for loop */
975
976 af_shaper_buf_destroy( face, shaper_buf );
977
978 if ( axis->blue_count )
979 {
980 /* we finally check whether blue zones are ordered; */
981 /* `ref' and `shoot' values of two blue zones must not overlap */
982
983 FT_UInt i;
984 AF_LatinBlue blue_sorted[AF_BLUE_STRINGSET_MAX_LEN + 2];
985
986
987 for ( i = 0; i < axis->blue_count; i++ )
988 blue_sorted[i] = &axis->blues[i];
989
990 /* sort bottoms of blue zones... */
991 af_latin_sort_blue( axis->blue_count, blue_sorted );
992
993 /* ...and adjust top values if necessary */
994 for ( i = 0; i < axis->blue_count - 1; i++ )
995 {
996 FT_Pos* a;
997 FT_Pos* b;
998
999#ifdef FT_DEBUG_LEVEL_TRACE
1000 FT_Bool a_is_top = 0;
1001#endif
1002
1003
1004 if ( blue_sorted[i]->flags & ( AF_LATIN_BLUE_TOP |
1005 AF_LATIN_BLUE_SUB_TOP ) )
1006 {
1007 a = &blue_sorted[i]->shoot.org;
1008#ifdef FT_DEBUG_LEVEL_TRACE
1009 a_is_top = 1;
1010#endif
1011 }
1012 else
1013 a = &blue_sorted[i]->ref.org;
1014
1015 if ( blue_sorted[i + 1]->flags & ( AF_LATIN_BLUE_TOP |
1016 AF_LATIN_BLUE_SUB_TOP ) )
1017 b = &blue_sorted[i + 1]->shoot.org;
1018 else
1019 b = &blue_sorted[i + 1]->ref.org;
1020
1021 if ( *a > *b )
1022 {
1023 *a = *b;
1024 FT_TRACE5(( "blue zone overlap:"
1025 " adjusting %s %td to %ld\n",
1026 a_is_top ? "overshoot" : "reference",
1027 blue_sorted[i] - axis->blues,
1028 *a ));
1029 }
1030 }
1031
1032 FT_TRACE5(( "\n" ));
1033
1034 return 0;
1035 }
1036 else
1037 {
1038 /* disable hinting for the current style if there are no blue zones */
1039
1040 AF_FaceGlobals globals = metrics->root.globals;
1041 FT_UShort* gstyles = globals->glyph_styles;
1042
1043 FT_UInt i;
1044
1045
1046 FT_TRACE5(( "no blue zones found:"
1047 " hinting disabled for this style\n" ));
1048
1049 for ( i = 0; i < globals->glyph_count; i++ )
1050 {
1051 if ( ( gstyles[i] & AF_STYLE_MASK ) == sc->style )
1052 gstyles[i] = AF_STYLE_NONE_DFLT;
1053 }
1054
1055 FT_TRACE5(( "\n" ));
1056
1057 return 1;
1058 }
1059 }
1060
1061
1062 /* Check whether all ASCII digits have the same advance width. */
1063
1064 FT_LOCAL_DEF( void )
1065 af_latin_metrics_check_digits( AF_LatinMetrics metrics,
1066 FT_Face face )
1067 {
1068 FT_Bool started = 0, same_width = 1;
1069 FT_Long advance = 0, old_advance = 0;
1070
1071 /* If HarfBuzz is not available, we need a pointer to a single */
1072 /* unsigned long value. */
1073#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
1074 void* shaper_buf;
1075#else
1076 FT_ULong shaper_buf_;
1077 void* shaper_buf = &shaper_buf_;
1078#endif
1079
1080 /* in all supported charmaps, digits have character codes 0x30-0x39 */
1081 const char digits[] = "0 1 2 3 4 5 6 7 8 9";
1082 const char* p;
1083
1084
1085 p = digits;
1086
1087#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
1088 shaper_buf = af_shaper_buf_create( face );
1089#endif
1090
1091 while ( *p )
1092 {
1093 FT_ULong glyph_index;
1094 unsigned int num_idx;
1095
1096
1097 /* reject input that maps to more than a single glyph */
1098 p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
1099 if ( num_idx > 1 )
1100 continue;
1101
1102 glyph_index = af_shaper_get_elem( &metrics->root,
1103 shaper_buf,
1104 0,
1105 &advance,
1106 NULL );
1107 if ( !glyph_index )
1108 continue;
1109
1110 if ( started )
1111 {
1112 if ( advance != old_advance )
1113 {
1114 same_width = 0;
1115 break;
1116 }
1117 }
1118 else
1119 {
1120 old_advance = advance;
1121 started = 1;
1122 }
1123 }
1124
1125 af_shaper_buf_destroy( face, shaper_buf );
1126
1127 metrics->root.digits_have_same_width = same_width;
1128 }
1129
1130
1131 /* Initialize global metrics. */
1132
1133 FT_LOCAL_DEF( FT_Error )
1134 af_latin_metrics_init( AF_StyleMetrics metrics_, /* AF_LatinMetrics */
1135 FT_Face face )
1136 {
1137 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
1138
1139 FT_Error error = FT_Err_Ok;
1140
1141 FT_CharMap oldmap = face->charmap;
1142
1143
1144 metrics->units_per_em = face->units_per_EM;
1145
1146 if ( !FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
1147 {
1148 af_latin_metrics_init_widths( metrics, face );
1149 if ( af_latin_metrics_init_blues( metrics, face ) )
1150 {
1151 /* use internal error code to indicate missing blue zones */
1152 error = -1;
1153 goto Exit;
1154 }
1155 af_latin_metrics_check_digits( metrics, face );
1156 }
1157
1158 Exit:
1159 face->charmap = oldmap;
1160 return error;
1161 }
1162
1163
1164 /* Adjust scaling value, then scale and shift widths */
1165 /* and blue zones (if applicable) for given dimension. */
1166
1167 static void
1168 af_latin_metrics_scale_dim( AF_LatinMetrics metrics,
1169 AF_Scaler scaler,
1170 AF_Dimension dim )
1171 {
1172 FT_Fixed scale;
1173 FT_Pos delta;
1174 AF_LatinAxis axis;
1175 FT_UInt nn;
1176
1177
1178 if ( dim == AF_DIMENSION_HORZ )
1179 {
1180 scale = scaler->x_scale;
1181 delta = scaler->x_delta;
1182 }
1183 else
1184 {
1185 scale = scaler->y_scale;
1186 delta = scaler->y_delta;
1187 }
1188
1189 axis = &metrics->axis[dim];
1190
1191 if ( axis->org_scale == scale && axis->org_delta == delta )
1192 return;
1193
1194 axis->org_scale = scale;
1195 axis->org_delta = delta;
1196
1197 /*
1198 * correct X and Y scale to optimize the alignment of the top of small
1199 * letters to the pixel grid
1200 */
1201 {
1202 AF_LatinAxis Axis = &metrics->axis[AF_DIMENSION_VERT];
1203 AF_LatinBlue blue = NULL;
1204
1205
1206 for ( nn = 0; nn < Axis->blue_count; nn++ )
1207 {
1208 if ( Axis->blues[nn].flags & AF_LATIN_BLUE_ADJUSTMENT )
1209 {
1210 blue = &Axis->blues[nn];
1211 break;
1212 }
1213 }
1214
1215 if ( blue )
1216 {
1217 FT_Pos scaled;
1218 FT_Pos threshold;
1219 FT_Pos fitted;
1220 FT_UInt limit;
1221 FT_UInt ppem;
1222
1223
1224 scaled = FT_MulFix( blue->shoot.org, scale );
1225 ppem = metrics->root.scaler.face->size->metrics.x_ppem;
1226 limit = metrics->root.globals->increase_x_height;
1227 threshold = 40;
1228
1229 /* if the `increase-x-height' property is active, */
1230 /* we round up much more often */
1231 if ( limit &&
1232 ppem <= limit &&
1233 ppem >= AF_PROP_INCREASE_X_HEIGHT_MIN )
1234 threshold = 52;
1235
1236 fitted = ( scaled + threshold ) & ~63;
1237
1238 if ( scaled != fitted )
1239 {
1240#if 0
1241 if ( dim == AF_DIMENSION_HORZ )
1242 {
1243 if ( fitted < scaled )
1244 scale -= scale / 50; /* scale *= 0.98 */
1245 }
1246 else
1247#endif
1248 if ( dim == AF_DIMENSION_VERT )
1249 {
1250 FT_Pos max_height;
1251 FT_Pos dist;
1252 FT_Fixed new_scale;
1253
1254
1255 new_scale = FT_MulDiv( scale, fitted, scaled );
1256
1257 /* the scaling should not change the result by more than two pixels */
1258 max_height = metrics->units_per_em;
1259
1260 for ( nn = 0; nn < Axis->blue_count; nn++ )
1261 {
1262 max_height = FT_MAX( max_height, Axis->blues[nn].ascender );
1263 max_height = FT_MAX( max_height, -Axis->blues[nn].descender );
1264 }
1265
1266 dist = FT_ABS( FT_MulFix( max_height, new_scale - scale ) );
1267 dist &= ~127;
1268
1269 if ( dist == 0 )
1270 {
1271 FT_TRACE5(( "af_latin_metrics_scale_dim:"
1272 " x height alignment (style `%s'):\n",
1273 af_style_names[metrics->root.style_class->style] ));
1274 FT_TRACE5(( " "
1275 " vertical scaling changed"
1276 " from %.5f to %.5f (by %ld%%)\n",
1277 (double)scale / 65536,
1278 (double)new_scale / 65536,
1279 ( fitted - scaled ) * 100 / scaled ));
1280 FT_TRACE5(( "\n" ));
1281
1282 scale = new_scale;
1283 }
1284#ifdef FT_DEBUG_LEVEL_TRACE
1285 else
1286 {
1287 FT_TRACE5(( "af_latin_metrics_scale_dim:"
1288 " x height alignment (style `%s'):\n",
1289 af_style_names[metrics->root.style_class->style] ));
1290 FT_TRACE5(( " "
1291 " excessive vertical scaling abandoned\n" ));
1292 FT_TRACE5(( "\n" ));
1293 }
1294#endif
1295 }
1296 }
1297 }
1298 }
1299
1300 axis->scale = scale;
1301 axis->delta = delta;
1302
1303 if ( dim == AF_DIMENSION_HORZ )
1304 {
1305 metrics->root.scaler.x_scale = scale;
1306 metrics->root.scaler.x_delta = delta;
1307 }
1308 else
1309 {
1310 metrics->root.scaler.y_scale = scale;
1311 metrics->root.scaler.y_delta = delta;
1312 }
1313
1314 FT_TRACE5(( "%s widths (style `%s')\n",
1315 dim == AF_DIMENSION_HORZ ? "horizontal" : "vertical",
1316 af_style_names[metrics->root.style_class->style] ));
1317
1318 /* scale the widths */
1319 for ( nn = 0; nn < axis->width_count; nn++ )
1320 {
1321 AF_Width width = axis->widths + nn;
1322
1323
1324 width->cur = FT_MulFix( width->org, scale );
1325 width->fit = width->cur;
1326
1327 FT_TRACE5(( " %ld scaled to %.2f\n",
1328 width->org,
1329 (double)width->cur / 64 ));
1330 }
1331
1332 FT_TRACE5(( "\n" ));
1333
1334 /* an extra-light axis corresponds to a standard width that is */
1335 /* smaller than 5/8 pixels */
1336 axis->extra_light =
1337 FT_BOOL( FT_MulFix( axis->standard_width, scale ) < 32 + 8 );
1338
1339#ifdef FT_DEBUG_LEVEL_TRACE
1340 if ( axis->extra_light )
1341 {
1342 FT_TRACE5(( "`%s' style is extra light (at current resolution)\n",
1343 af_style_names[metrics->root.style_class->style] ));
1344 FT_TRACE5(( "\n" ));
1345 }
1346#endif
1347
1348 if ( dim == AF_DIMENSION_VERT )
1349 {
1350#ifdef FT_DEBUG_LEVEL_TRACE
1351 if ( axis->blue_count )
1352 FT_TRACE5(( "blue zones (style `%s')\n",
1353 af_style_names[metrics->root.style_class->style] ));
1354#endif
1355
1356 /* scale the blue zones */
1357 for ( nn = 0; nn < axis->blue_count; nn++ )
1358 {
1359 AF_LatinBlue blue = &axis->blues[nn];
1360 FT_Pos dist;
1361
1362
1363 blue->ref.cur = FT_MulFix( blue->ref.org, scale ) + delta;
1364 blue->ref.fit = blue->ref.cur;
1365 blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta;
1366 blue->shoot.fit = blue->shoot.cur;
1367 blue->flags &= ~AF_LATIN_BLUE_ACTIVE;
1368
1369 /* a blue zone is only active if it is less than 3/4 pixels tall */
1370 dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale );
1371 if ( dist <= 48 && dist >= -48 )
1372 {
1373#if 0
1374 FT_Pos delta1;
1375#endif
1376 FT_Pos delta2;
1377
1378
1379 /* use discrete values for blue zone widths */
1380
1381#if 0
1382
1383 /* generic, original code */
1384 delta1 = blue->shoot.org - blue->ref.org;
1385 delta2 = delta1;
1386 if ( delta1 < 0 )
1387 delta2 = -delta2;
1388
1389 delta2 = FT_MulFix( delta2, scale );
1390
1391 if ( delta2 < 32 )
1392 delta2 = 0;
1393 else if ( delta2 < 64 )
1394 delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & ~31 );
1395 else
1396 delta2 = FT_PIX_ROUND( delta2 );
1397
1398 if ( delta1 < 0 )
1399 delta2 = -delta2;
1400
1401 blue->ref.fit = FT_PIX_ROUND( blue->ref.cur );
1402 blue->shoot.fit = blue->ref.fit + delta2;
1403
1404#else
1405
1406 /* simplified version due to abs(dist) <= 48 */
1407 delta2 = dist;
1408 if ( dist < 0 )
1409 delta2 = -delta2;
1410
1411 if ( delta2 < 32 )
1412 delta2 = 0;
1413 else if ( delta2 < 48 )
1414 delta2 = 32;
1415 else
1416 delta2 = 64;
1417
1418 if ( dist < 0 )
1419 delta2 = -delta2;
1420
1421 blue->ref.fit = FT_PIX_ROUND( blue->ref.cur );
1422 blue->shoot.fit = blue->ref.fit - delta2;
1423
1424#endif
1425
1426 blue->flags |= AF_LATIN_BLUE_ACTIVE;
1427 }
1428 }
1429
1430 /* use sub-top blue zone only if it doesn't overlap with */
1431 /* another (non-sup-top) blue zone; otherwise, the */
1432 /* effect would be similar to a neutral blue zone, which */
1433 /* is not desired here */
1434 for ( nn = 0; nn < axis->blue_count; nn++ )
1435 {
1436 AF_LatinBlue blue = &axis->blues[nn];
1437 FT_UInt i;
1438
1439
1440 if ( !( blue->flags & AF_LATIN_BLUE_SUB_TOP ) )
1441 continue;
1442 if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) )
1443 continue;
1444
1445 for ( i = 0; i < axis->blue_count; i++ )
1446 {
1447 AF_LatinBlue b = &axis->blues[i];
1448
1449
1450 if ( b->flags & AF_LATIN_BLUE_SUB_TOP )
1451 continue;
1452 if ( !( b->flags & AF_LATIN_BLUE_ACTIVE ) )
1453 continue;
1454
1455 if ( b->ref.fit <= blue->shoot.fit &&
1456 b->shoot.fit >= blue->ref.fit )
1457 {
1458 blue->flags &= ~AF_LATIN_BLUE_ACTIVE;
1459 break;
1460 }
1461 }
1462 }
1463
1464#ifdef FT_DEBUG_LEVEL_TRACE
1465 for ( nn = 0; nn < axis->blue_count; nn++ )
1466 {
1467 AF_LatinBlue blue = &axis->blues[nn];
1468
1469
1470 FT_TRACE5(( " reference %d: %ld scaled to %.2f%s\n",
1471 nn,
1472 blue->ref.org,
1473 (double)blue->ref.fit / 64,
1474 ( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? ""
1475 : " (inactive)" ));
1476 FT_TRACE5(( " overshoot %d: %ld scaled to %.2f%s\n",
1477 nn,
1478 blue->shoot.org,
1479 (double)blue->shoot.fit / 64,
1480 ( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? ""
1481 : " (inactive)" ));
1482 }
1483#endif
1484 }
1485 }
1486
1487
1488 /* Scale global values in both directions. */
1489
1490 FT_LOCAL_DEF( void )
1491 af_latin_metrics_scale( AF_StyleMetrics metrics_, /* AF_LatinMetrics */
1492 AF_Scaler scaler )
1493 {
1494 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
1495
1496
1497 metrics->root.scaler.render_mode = scaler->render_mode;
1498 metrics->root.scaler.face = scaler->face;
1499 metrics->root.scaler.flags = scaler->flags;
1500
1501 af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
1502 af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
1503 }
1504
1505
1506 /* Extract standard_width from writing system/script specific */
1507 /* metrics class. */
1508
1509 FT_CALLBACK_DEF( void )
1510 af_latin_get_standard_widths( AF_StyleMetrics metrics_, /* AF_LatinMetrics */
1511 FT_Pos* stdHW,
1512 FT_Pos* stdVW )
1513 {
1514 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
1515
1516
1517 if ( stdHW )
1518 *stdHW = metrics->axis[AF_DIMENSION_VERT].standard_width;
1519
1520 if ( stdVW )
1521 *stdVW = metrics->axis[AF_DIMENSION_HORZ].standard_width;
1522 }
1523
1524
1525 /*************************************************************************/
1526 /*************************************************************************/
1527 /***** *****/
1528 /***** L A T I N G L Y P H A N A L Y S I S *****/
1529 /***** *****/
1530 /*************************************************************************/
1531 /*************************************************************************/
1532
1533
1534 /* Walk over all contours and compute its segments. */
1535
1536 FT_LOCAL_DEF( FT_Error )
1537 af_latin_hints_compute_segments( AF_GlyphHints hints,
1538 AF_Dimension dim )
1539 {
1540 AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics;
1541 AF_AxisHints axis = &hints->axis[dim];
1542 FT_Memory memory = hints->memory;
1543 FT_Error error = FT_Err_Ok;
1544 AF_Segment segment = NULL;
1545 AF_SegmentRec seg0;
1546 AF_Point* contour = hints->contours;
1547 AF_Point* contour_limit = contour + hints->num_contours;
1548 AF_Direction major_dir, segment_dir;
1549
1550 FT_Pos flat_threshold = FLAT_THRESHOLD( metrics->units_per_em );
1551
1552
1553 FT_ZERO( &seg0 );
1554 seg0.score = 32000;
1555 seg0.flags = AF_EDGE_NORMAL;
1556
1557 major_dir = (AF_Direction)FT_ABS( axis->major_dir );
1558 segment_dir = major_dir;
1559
1560 axis->num_segments = 0;
1561
1562 /* set up (u,v) in each point */
1563 if ( dim == AF_DIMENSION_HORZ )
1564 {
1565 AF_Point point = hints->points;
1566 AF_Point limit = point + hints->num_points;
1567
1568
1569 for ( ; point < limit; point++ )
1570 {
1571 point->u = point->fx;
1572 point->v = point->fy;
1573 }
1574 }
1575 else
1576 {
1577 AF_Point point = hints->points;
1578 AF_Point limit = point + hints->num_points;
1579
1580
1581 for ( ; point < limit; point++ )
1582 {
1583 point->u = point->fy;
1584 point->v = point->fx;
1585 }
1586 }
1587
1588 /* do each contour separately */
1589 for ( ; contour < contour_limit; contour++ )
1590 {
1591 AF_Point point = contour[0];
1592 AF_Point last = point->prev;
1593 int on_edge = 0;
1594
1595 /* we call values measured along a segment (point->v) */
1596 /* `coordinates', and values orthogonal to it (point->u) */
1597 /* `positions' */
1598 FT_Pos min_pos = 32000;
1599 FT_Pos max_pos = -32000;
1600 FT_Pos min_coord = 32000;
1601 FT_Pos max_coord = -32000;
1602 FT_UShort min_flags = AF_FLAG_NONE;
1603 FT_UShort max_flags = AF_FLAG_NONE;
1604 FT_Pos min_on_coord = 32000;
1605 FT_Pos max_on_coord = -32000;
1606
1607 FT_Bool passed;
1608
1609 AF_Segment prev_segment = NULL;
1610
1611 FT_Pos prev_min_pos = min_pos;
1612 FT_Pos prev_max_pos = max_pos;
1613 FT_Pos prev_min_coord = min_coord;
1614 FT_Pos prev_max_coord = max_coord;
1615 FT_UShort prev_min_flags = min_flags;
1616 FT_UShort prev_max_flags = max_flags;
1617 FT_Pos prev_min_on_coord = min_on_coord;
1618 FT_Pos prev_max_on_coord = max_on_coord;
1619
1620
1621 if ( FT_ABS( last->out_dir ) == major_dir &&
1622 FT_ABS( point->out_dir ) == major_dir )
1623 {
1624 /* we are already on an edge, try to locate its start */
1625 last = point;
1626
1627 for (;;)
1628 {
1629 point = point->prev;
1630 if ( FT_ABS( point->out_dir ) != major_dir )
1631 {
1632 point = point->next;
1633 break;
1634 }
1635 if ( point == last )
1636 break;
1637 }
1638 }
1639
1640 last = point;
1641 passed = 0;
1642
1643 for (;;)
1644 {
1645 FT_Pos u, v;
1646
1647
1648 if ( on_edge )
1649 {
1650 /* get minimum and maximum position */
1651 u = point->u;
1652 if ( u < min_pos )
1653 min_pos = u;
1654 if ( u > max_pos )
1655 max_pos = u;
1656
1657 /* get minimum and maximum coordinate together with flags */
1658 v = point->v;
1659 if ( v < min_coord )
1660 {
1661 min_coord = v;
1662 min_flags = point->flags;
1663 }
1664 if ( v > max_coord )
1665 {
1666 max_coord = v;
1667 max_flags = point->flags;
1668 }
1669
1670 /* get minimum and maximum coordinate of `on' points */
1671 if ( !( point->flags & AF_FLAG_CONTROL ) )
1672 {
1673 v = point->v;
1674 if ( v < min_on_coord )
1675 min_on_coord = v;
1676 if ( v > max_on_coord )
1677 max_on_coord = v;
1678 }
1679
1680 if ( point->out_dir != segment_dir || point == last )
1681 {
1682 /* check whether the new segment's start point is identical to */
1683 /* the previous segment's end point; for example, this might */
1684 /* happen for spikes */
1685
1686 if ( !prev_segment || segment->first != prev_segment->last )
1687 {
1688 /* points are different: we are just leaving an edge, thus */
1689 /* record a new segment */
1690
1691 segment->last = point;
1692 segment->pos = (FT_Short)( ( min_pos + max_pos ) >> 1 );
1693 segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 );
1694
1695 /* a segment is round if either its first or last point */
1696 /* is a control point, and the length of the on points */
1697 /* inbetween doesn't exceed a heuristic limit */
1698 if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL &&
1699 ( max_on_coord - min_on_coord ) < flat_threshold )
1700 segment->flags |= AF_EDGE_ROUND;
1701
1702 segment->min_coord = (FT_Short)min_coord;
1703 segment->max_coord = (FT_Short)max_coord;
1704 segment->height = segment->max_coord - segment->min_coord;
1705
1706 prev_segment = segment;
1707 prev_min_pos = min_pos;
1708 prev_max_pos = max_pos;
1709 prev_min_coord = min_coord;
1710 prev_max_coord = max_coord;
1711 prev_min_flags = min_flags;
1712 prev_max_flags = max_flags;
1713 prev_min_on_coord = min_on_coord;
1714 prev_max_on_coord = max_on_coord;
1715 }
1716 else
1717 {
1718 /* points are the same: we don't create a new segment but */
1719 /* merge the current segment with the previous one */
1720
1721 if ( prev_segment->last->in_dir == point->in_dir )
1722 {
1723 /* we have identical directions (this can happen for */
1724 /* degenerate outlines that move zig-zag along the main */
1725 /* axis without changing the coordinate value of the other */
1726 /* axis, and where the segments have just been merged): */
1727 /* unify segments */
1728
1729 /* update constraints */
1730
1731 if ( prev_min_pos < min_pos )
1732 min_pos = prev_min_pos;
1733 if ( prev_max_pos > max_pos )
1734 max_pos = prev_max_pos;
1735
1736 if ( prev_min_coord < min_coord )
1737 {
1738 min_coord = prev_min_coord;
1739 min_flags = prev_min_flags;
1740 }
1741 if ( prev_max_coord > max_coord )
1742 {
1743 max_coord = prev_max_coord;
1744 max_flags = prev_max_flags;
1745 }
1746
1747 if ( prev_min_on_coord < min_on_coord )
1748 min_on_coord = prev_min_on_coord;
1749 if ( prev_max_on_coord > max_on_coord )
1750 max_on_coord = prev_max_on_coord;
1751
1752 prev_segment->last = point;
1753 prev_segment->pos = (FT_Short)( ( min_pos +
1754 max_pos ) >> 1 );
1755 prev_segment->delta = (FT_Short)( ( max_pos -
1756 min_pos ) >> 1 );
1757
1758 if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL &&
1759 ( max_on_coord - min_on_coord ) < flat_threshold )
1760 prev_segment->flags |= AF_EDGE_ROUND;
1761 else
1762 prev_segment->flags &= ~AF_EDGE_ROUND;
1763
1764 prev_segment->min_coord = (FT_Short)min_coord;
1765 prev_segment->max_coord = (FT_Short)max_coord;
1766 prev_segment->height = prev_segment->max_coord -
1767 prev_segment->min_coord;
1768 }
1769 else
1770 {
1771 /* we have different directions; use the properties of the */
1772 /* longer segment and discard the other one */
1773
1774 if ( FT_ABS( prev_max_coord - prev_min_coord ) >
1775 FT_ABS( max_coord - min_coord ) )
1776 {
1777 /* discard current segment */
1778
1779 if ( min_pos < prev_min_pos )
1780 prev_min_pos = min_pos;
1781 if ( max_pos > prev_max_pos )
1782 prev_max_pos = max_pos;
1783
1784 prev_segment->last = point;
1785 prev_segment->pos = (FT_Short)( ( prev_min_pos +
1786 prev_max_pos ) >> 1 );
1787 prev_segment->delta = (FT_Short)( ( prev_max_pos -
1788 prev_min_pos ) >> 1 );
1789 }
1790 else
1791 {
1792 /* discard previous segment */
1793
1794 if ( prev_min_pos < min_pos )
1795 min_pos = prev_min_pos;
1796 if ( prev_max_pos > max_pos )
1797 max_pos = prev_max_pos;
1798
1799 segment->last = point;
1800 segment->pos = (FT_Short)( ( min_pos + max_pos ) >> 1 );
1801 segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 );
1802
1803 if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL &&
1804 ( max_on_coord - min_on_coord ) < flat_threshold )
1805 segment->flags |= AF_EDGE_ROUND;
1806
1807 segment->min_coord = (FT_Short)min_coord;
1808 segment->max_coord = (FT_Short)max_coord;
1809 segment->height = segment->max_coord -
1810 segment->min_coord;
1811
1812 *prev_segment = *segment;
1813
1814 prev_min_pos = min_pos;
1815 prev_max_pos = max_pos;
1816 prev_min_coord = min_coord;
1817 prev_max_coord = max_coord;
1818 prev_min_flags = min_flags;
1819 prev_max_flags = max_flags;
1820 prev_min_on_coord = min_on_coord;
1821 prev_max_on_coord = max_on_coord;
1822 }
1823 }
1824
1825 axis->num_segments--;
1826 }
1827
1828 on_edge = 0;
1829 segment = NULL;
1830
1831 /* fall through */
1832 }
1833 }
1834
1835 /* now exit if we are at the start/end point */
1836 if ( point == last )
1837 {
1838 if ( passed )
1839 break;
1840 passed = 1;
1841 }
1842
1843 /* if we are not on an edge, check whether the major direction */
1844 /* coincides with the current point's `out' direction, or */
1845 /* whether we have a single-point contour */
1846 if ( !on_edge &&
1847 ( FT_ABS( point->out_dir ) == major_dir ||
1848 point == point->prev ) )
1849 {
1850 /*
1851 * For efficiency, we restrict the number of segments to 1000,
1852 * which is a heuristic value: it is very unlikely that a glyph
1853 * with so many segments can be hinted in a sensible way.
1854 * Reasons:
1855 *
1856 * - The glyph has really 1000 segments; this implies that it has
1857 * at least 2000 outline points. Assuming 'normal' fonts that
1858 * have superfluous points optimized away, viewing such a glyph
1859 * only makes sense at large magnifications where hinting
1860 * isn't applied anyway.
1861 *
1862 * - We have a broken glyph. Hinting doesn't make sense in this
1863 * case either.
1864 */
1865 if ( axis->num_segments > 1000 )
1866 {
1867 FT_TRACE0(( "af_latin_hints_compute_segments:"
1868 " more than 1000 segments in this glyph;\n" ));
1869 FT_TRACE0(( " "
1870 " hinting is suppressed\n" ));
1871 axis->num_segments = 0;
1872 return FT_Err_Ok;
1873 }
1874
1875 /* this is the start of a new segment! */
1876 segment_dir = (AF_Direction)point->out_dir;
1877
1878 error = af_axis_hints_new_segment( axis, memory, &segment );
1879 if ( error )
1880 goto Exit;
1881
1882 /* clear all segment fields */
1883 segment[0] = seg0;
1884
1885 segment->dir = (FT_Char)segment_dir;
1886 segment->first = point;
1887 segment->last = point;
1888
1889 /* `af_axis_hints_new_segment' reallocates memory, */
1890 /* thus we have to refresh the `prev_segment' pointer */
1891 if ( prev_segment )
1892 prev_segment = segment - 1;
1893
1894 min_pos = max_pos = point->u;
1895 min_coord = max_coord = point->v;
1896 min_flags = max_flags = point->flags;
1897
1898 if ( point->flags & AF_FLAG_CONTROL )
1899 {
1900 min_on_coord = 32000;
1901 max_on_coord = -32000;
1902 }
1903 else
1904 min_on_coord = max_on_coord = point->v;
1905
1906 on_edge = 1;
1907
1908 if ( point == point->prev )
1909 {
1910 /* we have a one-point segment: this is a one-point */
1911 /* contour with `in' and `out' direction set to */
1912 /* AF_DIR_NONE */
1913 segment->pos = (FT_Short)min_pos;
1914
1915 if (point->flags & AF_FLAG_CONTROL)
1916 segment->flags |= AF_EDGE_ROUND;
1917
1918 segment->min_coord = (FT_Short)point->v;
1919 segment->max_coord = (FT_Short)point->v;
1920 segment->height = 0;
1921
1922 on_edge = 0;
1923 segment = NULL;
1924 }
1925 }
1926
1927 point = point->next;
1928 }
1929
1930 } /* contours */
1931
1932
1933 /* now slightly increase the height of segments if this makes */
1934 /* sense -- this is used to better detect and ignore serifs */
1935 {
1936 AF_Segment segments = axis->segments;
1937 AF_Segment segments_end = FT_OFFSET( segments, axis->num_segments );
1938
1939
1940 for ( segment = segments; segment < segments_end; segment++ )
1941 {
1942 AF_Point first = segment->first;
1943 AF_Point last = segment->last;
1944 FT_Pos first_v = first->v;
1945 FT_Pos last_v = last->v;
1946
1947
1948 if ( first_v < last_v )
1949 {
1950 AF_Point p;
1951
1952
1953 p = first->prev;
1954 if ( p->v < first_v )
1955 segment->height = (FT_Short)( segment->height +
1956 ( ( first_v - p->v ) >> 1 ) );
1957
1958 p = last->next;
1959 if ( p->v > last_v )
1960 segment->height = (FT_Short)( segment->height +
1961 ( ( p->v - last_v ) >> 1 ) );
1962 }
1963 else
1964 {
1965 AF_Point p;
1966
1967
1968 p = first->prev;
1969 if ( p->v > first_v )
1970 segment->height = (FT_Short)( segment->height +
1971 ( ( p->v - first_v ) >> 1 ) );
1972
1973 p = last->next;
1974 if ( p->v < last_v )
1975 segment->height = (FT_Short)( segment->height +
1976 ( ( last_v - p->v ) >> 1 ) );
1977 }
1978 }
1979 }
1980
1981 Exit:
1982 return error;
1983 }
1984
1985
1986 /* Link segments to form stems and serifs. If `width_count' and */
1987 /* `widths' are non-zero, use them to fine-tune the scoring function. */
1988
1989 FT_LOCAL_DEF( void )
1990 af_latin_hints_link_segments( AF_GlyphHints hints,
1991 FT_UInt width_count,
1992 AF_WidthRec* widths,
1993 AF_Dimension dim )
1994 {
1995 AF_AxisHints axis = &hints->axis[dim];
1996 AF_Segment segments = axis->segments;
1997 AF_Segment segment_limit = FT_OFFSET( segments, axis->num_segments );
1998 FT_Pos len_threshold, len_score, dist_score, max_width;
1999 AF_Segment seg1, seg2;
2000
2001
2002 if ( width_count )
2003 max_width = widths[width_count - 1].org;
2004 else
2005 max_width = 0;
2006
2007 /* a heuristic value to set up a minimum value for overlapping */
2008 len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 );
2009 if ( len_threshold == 0 )
2010 len_threshold = 1;
2011
2012 /* a heuristic value to weight lengths */
2013 len_score = AF_LATIN_CONSTANT( hints->metrics, 6000 );
2014
2015 /* a heuristic value to weight distances (no call to */
2016 /* AF_LATIN_CONSTANT needed, since we work on multiples */
2017 /* of the stem width) */
2018 dist_score = 3000;
2019
2020 /* now compare each segment to the others */
2021 for ( seg1 = segments; seg1 < segment_limit; seg1++ )
2022 {
2023 if ( seg1->dir != axis->major_dir )
2024 continue;
2025
2026 /* search for stems having opposite directions, */
2027 /* with seg1 to the `left' of seg2 */
2028 for ( seg2 = segments; seg2 < segment_limit; seg2++ )
2029 {
2030 FT_Pos pos1 = seg1->pos;
2031 FT_Pos pos2 = seg2->pos;
2032
2033
2034 if ( seg1->dir + seg2->dir == 0 && pos2 > pos1 )
2035 {
2036 /* compute distance between the two segments */
2037 FT_Pos min = seg1->min_coord;
2038 FT_Pos max = seg1->max_coord;
2039 FT_Pos len;
2040
2041
2042 if ( min < seg2->min_coord )
2043 min = seg2->min_coord;
2044
2045 if ( max > seg2->max_coord )
2046 max = seg2->max_coord;
2047
2048 /* compute maximum coordinate difference of the two segments */
2049 /* (that is, how much they overlap) */
2050 len = max - min;
2051 if ( len >= len_threshold )
2052 {
2053 /*
2054 * The score is the sum of two demerits indicating the
2055 * `badness' of a fit, measured along the segments' main axis
2056 * and orthogonal to it, respectively.
2057 *
2058 * - The less overlapping along the main axis, the worse it
2059 * is, causing a larger demerit.
2060 *
2061 * - The nearer the orthogonal distance to a stem width, the
2062 * better it is, causing a smaller demerit. For simplicity,
2063 * however, we only increase the demerit for values that
2064 * exceed the largest stem width.
2065 */
2066
2067 FT_Pos dist = pos2 - pos1;
2068
2069 FT_Pos dist_demerit, score;
2070
2071
2072 if ( max_width )
2073 {
2074 /* distance demerits are based on multiples of `max_width'; */
2075 /* we scale by 1024 for getting more precision */
2076 FT_Pos delta = ( dist << 10 ) / max_width - ( 1 << 10 );
2077
2078
2079 if ( delta > 10000 )
2080 dist_demerit = 32000;
2081 else if ( delta > 0 )
2082 dist_demerit = delta * delta / dist_score;
2083 else
2084 dist_demerit = 0;
2085 }
2086 else
2087 dist_demerit = dist; /* default if no widths available */
2088
2089 score = dist_demerit + len_score / len;
2090
2091 /* and we search for the smallest score */
2092 if ( score < seg1->score )
2093 {
2094 seg1->score = score;
2095 seg1->link = seg2;
2096 }
2097
2098 if ( score < seg2->score )
2099 {
2100 seg2->score = score;
2101 seg2->link = seg1;
2102 }
2103 }
2104 }
2105 }
2106 }
2107
2108 /* now compute the `serif' segments, cf. explanations in `afhints.h' */
2109 for ( seg1 = segments; seg1 < segment_limit; seg1++ )
2110 {
2111 seg2 = seg1->link;
2112
2113 if ( seg2 )
2114 {
2115 if ( seg2->link != seg1 )
2116 {
2117 seg1->link = NULL;
2118 seg1->serif = seg2->link;
2119 }
2120 }
2121 }
2122 }
2123
2124
2125 /* Link segments to edges, using feature analysis for selection. */
2126
2127 FT_LOCAL_DEF( FT_Error )
2128 af_latin_hints_compute_edges( AF_GlyphHints hints,
2129 AF_Dimension dim )
2130 {
2131 AF_AxisHints axis = &hints->axis[dim];
2132 FT_Error error = FT_Err_Ok;
2133 FT_Memory memory = hints->memory;
2134 AF_LatinAxis laxis = &((AF_LatinMetrics)hints->metrics)->axis[dim];
2135
2136 AF_StyleClass style_class = hints->metrics->style_class;
2137 AF_ScriptClass script_class = af_script_classes[style_class->script];
2138
2139 FT_Bool top_to_bottom_hinting = 0;
2140
2141 AF_Segment segments = axis->segments;
2142 AF_Segment segment_limit = FT_OFFSET( segments, axis->num_segments );
2143 AF_Segment seg;
2144
2145#if 0
2146 AF_Direction up_dir;
2147#endif
2148 FT_Fixed scale;
2149 FT_Pos edge_distance_threshold;
2150 FT_Pos segment_length_threshold;
2151 FT_Pos segment_width_threshold;
2152
2153
2154 axis->num_edges = 0;
2155
2156 scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
2157 : hints->y_scale;
2158
2159#if 0
2160 up_dir = ( dim == AF_DIMENSION_HORZ ) ? AF_DIR_UP
2161 : AF_DIR_RIGHT;
2162#endif
2163
2164 if ( dim == AF_DIMENSION_VERT )
2165 top_to_bottom_hinting = script_class->top_to_bottom_hinting;
2166
2167 /*
2168 * We ignore all segments that are less than 1 pixel in length
2169 * to avoid many problems with serif fonts. We compute the
2170 * corresponding threshold in font units.
2171 */
2172 if ( dim == AF_DIMENSION_HORZ )
2173 segment_length_threshold = FT_DivFix( 64, hints->y_scale );
2174 else
2175 segment_length_threshold = 0;
2176
2177 /*
2178 * Similarly, we ignore segments that have a width delta
2179 * larger than 0.5px (i.e., a width larger than 1px).
2180 */
2181 segment_width_threshold = FT_DivFix( 32, scale );
2182
2183 /**********************************************************************
2184 *
2185 * We begin by generating a sorted table of edges for the current
2186 * direction. To do so, we simply scan each segment and try to find
2187 * an edge in our table that corresponds to its position.
2188 *
2189 * If no edge is found, we create and insert a new edge in the
2190 * sorted table. Otherwise, we simply add the segment to the edge's
2191 * list which gets processed in the second step to compute the
2192 * edge's properties.
2193 *
2194 * Note that the table of edges is sorted along the segment/edge
2195 * position.
2196 *
2197 */
2198
2199 /* assure that edge distance threshold is at most 0.25px */
2200 edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
2201 scale );
2202 if ( edge_distance_threshold > 64 / 4 )
2203 edge_distance_threshold = 64 / 4;
2204
2205 edge_distance_threshold = FT_DivFix( edge_distance_threshold,
2206 scale );
2207
2208 for ( seg = segments; seg < segment_limit; seg++ )
2209 {
2210 AF_Edge found = NULL;
2211 FT_UInt ee;
2212
2213
2214 /* ignore too short segments, too wide ones, and, in this loop, */
2215 /* one-point segments without a direction */
2216 if ( seg->height < segment_length_threshold ||
2217 seg->delta > segment_width_threshold ||
2218 seg->dir == AF_DIR_NONE )
2219 continue;
2220
2221 /* A special case for serif edges: If they are smaller than */
2222 /* 1.5 pixels we ignore them. */
2223 if ( seg->serif &&
2224 2 * seg->height < 3 * segment_length_threshold )
2225 continue;
2226
2227 /* look for an edge corresponding to the segment */
2228 for ( ee = 0; ee < axis->num_edges; ee++ )
2229 {
2230 AF_Edge edge = axis->edges + ee;
2231 FT_Pos dist;
2232
2233
2234 dist = seg->pos - edge->fpos;
2235 if ( dist < 0 )
2236 dist = -dist;
2237
2238 if ( dist < edge_distance_threshold && edge->dir == seg->dir )
2239 {
2240 found = edge;
2241 break;
2242 }
2243 }
2244
2245 if ( !found )
2246 {
2247 AF_Edge edge;
2248
2249
2250 /* insert a new edge in the list and */
2251 /* sort according to the position */
2252 error = af_axis_hints_new_edge( axis, seg->pos,
2253 (AF_Direction)seg->dir,
2254 top_to_bottom_hinting,
2255 memory, &edge );
2256 if ( error )
2257 goto Exit;
2258
2259 /* add the segment to the new edge's list */
2260 FT_ZERO( edge );
2261
2262 edge->first = seg;
2263 edge->last = seg;
2264 edge->dir = seg->dir;
2265 edge->fpos = seg->pos;
2266 edge->opos = FT_MulFix( seg->pos, scale );
2267 edge->pos = edge->opos;
2268 seg->edge_next = seg;
2269 }
2270 else
2271 {
2272 /* if an edge was found, simply add the segment to the edge's */
2273 /* list */
2274 seg->edge_next = found->first;
2275 found->last->edge_next = seg;
2276 found->last = seg;
2277 }
2278 }
2279
2280 /* we loop again over all segments to catch one-point segments */
2281 /* without a direction: if possible, link them to existing edges */
2282 for ( seg = segments; seg < segment_limit; seg++ )
2283 {
2284 AF_Edge found = NULL;
2285 FT_UInt ee;
2286
2287
2288 if ( seg->dir != AF_DIR_NONE )
2289 continue;
2290
2291 /* look for an edge corresponding to the segment */
2292 for ( ee = 0; ee < axis->num_edges; ee++ )
2293 {
2294 AF_Edge edge = axis->edges + ee;
2295 FT_Pos dist;
2296
2297
2298 dist = seg->pos - edge->fpos;
2299 if ( dist < 0 )
2300 dist = -dist;
2301
2302 if ( dist < edge_distance_threshold )
2303 {
2304 found = edge;
2305 break;
2306 }
2307 }
2308
2309 /* one-point segments without a match are ignored */
2310 if ( found )
2311 {
2312 seg->edge_next = found->first;
2313 found->last->edge_next = seg;
2314 found->last = seg;
2315 }
2316 }
2317
2318
2319 /*******************************************************************
2320 *
2321 * Good, we now compute each edge's properties according to the
2322 * segments found on its position. Basically, these are
2323 *
2324 * - the edge's main direction
2325 * - stem edge, serif edge or both (which defaults to stem then)
2326 * - rounded edge, straight or both (which defaults to straight)
2327 * - link for edge
2328 *
2329 */
2330
2331 /* first of all, set the `edge' field in each segment -- this is */
2332 /* required in order to compute edge links */
2333
2334 /*
2335 * Note that removing this loop and setting the `edge' field of each
2336 * segment directly in the code above slows down execution speed for
2337 * some reasons on platforms like the Sun.
2338 */
2339 {
2340 AF_Edge edges = axis->edges;
2341 AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges );
2342 AF_Edge edge;
2343
2344
2345 for ( edge = edges; edge < edge_limit; edge++ )
2346 {
2347 seg = edge->first;
2348 if ( seg )
2349 do
2350 {
2351 seg->edge = edge;
2352 seg = seg->edge_next;
2353
2354 } while ( seg != edge->first );
2355 }
2356
2357 /* now compute each edge properties */
2358 for ( edge = edges; edge < edge_limit; edge++ )
2359 {
2360 FT_Int is_round = 0; /* does it contain round segments? */
2361 FT_Int is_straight = 0; /* does it contain straight segments? */
2362#if 0
2363 FT_Pos ups = 0; /* number of upwards segments */
2364 FT_Pos downs = 0; /* number of downwards segments */
2365#endif
2366
2367
2368 seg = edge->first;
2369
2370 do
2371 {
2372 FT_Bool is_serif;
2373
2374
2375 /* check for roundness of segment */
2376 if ( seg->flags & AF_EDGE_ROUND )
2377 is_round++;
2378 else
2379 is_straight++;
2380
2381#if 0
2382 /* check for segment direction */
2383 if ( seg->dir == up_dir )
2384 ups += seg->max_coord - seg->min_coord;
2385 else
2386 downs += seg->max_coord - seg->min_coord;
2387#endif
2388
2389 /* check for links -- if seg->serif is set, then seg->link must */
2390 /* be ignored */
2391 is_serif = FT_BOOL( seg->serif &&
2392 seg->serif->edge &&
2393 seg->serif->edge != edge );
2394
2395 if ( ( seg->link && seg->link->edge ) || is_serif )
2396 {
2397 AF_Edge edge2;
2398 AF_Segment seg2;
2399
2400
2401 edge2 = edge->link;
2402 seg2 = seg->link;
2403
2404 if ( is_serif )
2405 {
2406 seg2 = seg->serif;
2407 edge2 = edge->serif;
2408 }
2409
2410 if ( edge2 )
2411 {
2412 FT_Pos edge_delta;
2413 FT_Pos seg_delta;
2414
2415
2416 edge_delta = edge->fpos - edge2->fpos;
2417 if ( edge_delta < 0 )
2418 edge_delta = -edge_delta;
2419
2420 seg_delta = seg->pos - seg2->pos;
2421 if ( seg_delta < 0 )
2422 seg_delta = -seg_delta;
2423
2424 if ( seg_delta < edge_delta )
2425 edge2 = seg2->edge;
2426 }
2427 else
2428 edge2 = seg2->edge;
2429
2430 if ( is_serif )
2431 {
2432 edge->serif = edge2;
2433 edge2->flags |= AF_EDGE_SERIF;
2434 }
2435 else
2436 edge->link = edge2;
2437 }
2438
2439 seg = seg->edge_next;
2440
2441 } while ( seg != edge->first );
2442
2443 /* set the round/straight flags */
2444 edge->flags = AF_EDGE_NORMAL;
2445
2446 if ( is_round > 0 && is_round >= is_straight )
2447 edge->flags |= AF_EDGE_ROUND;
2448
2449#if 0
2450 /* set the edge's main direction */
2451 edge->dir = AF_DIR_NONE;
2452
2453 if ( ups > downs )
2454 edge->dir = (FT_Char)up_dir;
2455
2456 else if ( ups < downs )
2457 edge->dir = (FT_Char)-up_dir;
2458
2459 else if ( ups == downs )
2460 edge->dir = 0; /* both up and down! */
2461#endif
2462
2463 /* get rid of serifs if link is set */
2464 /* XXX: This gets rid of many unpleasant artefacts! */
2465 /* Example: the `c' in cour.pfa at size 13 */
2466
2467 if ( edge->serif && edge->link )
2468 edge->serif = NULL;
2469 }
2470 }
2471
2472 Exit:
2473 return error;
2474 }
2475
2476
2477 /* Detect segments and edges for given dimension. */
2478
2479 FT_LOCAL_DEF( FT_Error )
2480 af_latin_hints_detect_features( AF_GlyphHints hints,
2481 FT_UInt width_count,
2482 AF_WidthRec* widths,
2483 AF_Dimension dim )
2484 {
2485 FT_Error error;
2486
2487
2488 error = af_latin_hints_compute_segments( hints, dim );
2489 if ( !error )
2490 {
2491 af_latin_hints_link_segments( hints, width_count, widths, dim );
2492
2493 error = af_latin_hints_compute_edges( hints, dim );
2494 }
2495
2496 return error;
2497 }
2498
2499
2500 /* Compute all edges which lie within blue zones. */
2501
2502 static void
2503 af_latin_hints_compute_blue_edges( AF_GlyphHints hints,
2504 AF_LatinMetrics metrics )
2505 {
2506 AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT];
2507 AF_Edge edge = axis->edges;
2508 AF_Edge edge_limit = FT_OFFSET( edge, axis->num_edges );
2509 AF_LatinAxis latin = &metrics->axis[AF_DIMENSION_VERT];
2510 FT_Fixed scale = latin->scale;
2511
2512
2513 /* compute which blue zones are active, i.e. have their scaled */
2514 /* size < 3/4 pixels */
2515
2516 /* for each horizontal edge search the blue zone which is closest */
2517 for ( ; edge < edge_limit; edge++ )
2518 {
2519 FT_UInt bb;
2520 AF_Width best_blue = NULL;
2521 FT_Bool best_blue_is_neutral = 0;
2522 FT_Pos best_dist; /* initial threshold */
2523
2524
2525 /* compute the initial threshold as a fraction of the EM size */
2526 /* (the value 40 is heuristic) */
2527 best_dist = FT_MulFix( metrics->units_per_em / 40, scale );
2528
2529 /* assure a minimum distance of 0.5px */
2530 if ( best_dist > 64 / 2 )
2531 best_dist = 64 / 2;
2532
2533 for ( bb = 0; bb < latin->blue_count; bb++ )
2534 {
2535 AF_LatinBlue blue = latin->blues + bb;
2536 FT_Bool is_top_blue, is_neutral_blue, is_major_dir;
2537
2538
2539 /* skip inactive blue zones (i.e., those that are too large) */
2540 if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) )
2541 continue;
2542
2543 /* if it is a top zone, check for right edges (against the major */
2544 /* direction); if it is a bottom zone, check for left edges (in */
2545 /* the major direction) -- this assumes the TrueType convention */
2546 /* for the orientation of contours */
2547 is_top_blue =
2548 (FT_Byte)( ( blue->flags & ( AF_LATIN_BLUE_TOP |
2549 AF_LATIN_BLUE_SUB_TOP ) ) != 0 );
2550 is_neutral_blue =
2551 (FT_Byte)( ( blue->flags & AF_LATIN_BLUE_NEUTRAL ) != 0);
2552 is_major_dir =
2553 FT_BOOL( edge->dir == axis->major_dir );
2554
2555 /* neutral blue zones are handled for both directions */
2556 if ( is_top_blue ^ is_major_dir || is_neutral_blue )
2557 {
2558 FT_Pos dist;
2559
2560
2561 /* first of all, compare it to the reference position */
2562 dist = edge->fpos - blue->ref.org;
2563 if ( dist < 0 )
2564 dist = -dist;
2565
2566 dist = FT_MulFix( dist, scale );
2567 if ( dist < best_dist )
2568 {
2569 best_dist = dist;
2570 best_blue = &blue->ref;
2571 best_blue_is_neutral = is_neutral_blue;
2572 }
2573
2574 /* now compare it to the overshoot position and check whether */
2575 /* the edge is rounded, and whether the edge is over the */
2576 /* reference position of a top zone, or under the reference */
2577 /* position of a bottom zone (provided we don't have a */
2578 /* neutral blue zone) */
2579 if ( edge->flags & AF_EDGE_ROUND &&
2580 dist != 0 &&
2581 !is_neutral_blue )
2582 {
2583 FT_Bool is_under_ref = FT_BOOL( edge->fpos < blue->ref.org );
2584
2585
2586 if ( is_top_blue ^ is_under_ref )
2587 {
2588 dist = edge->fpos - blue->shoot.org;
2589 if ( dist < 0 )
2590 dist = -dist;
2591
2592 dist = FT_MulFix( dist, scale );
2593 if ( dist < best_dist )
2594 {
2595 best_dist = dist;
2596 best_blue = &blue->shoot;
2597 best_blue_is_neutral = is_neutral_blue;
2598 }
2599 }
2600 }
2601 }
2602 }
2603
2604 if ( best_blue )
2605 {
2606 edge->blue_edge = best_blue;
2607 if ( best_blue_is_neutral )
2608 edge->flags |= AF_EDGE_NEUTRAL;
2609 }
2610 }
2611 }
2612
2613
2614 /* Initalize hinting engine. */
2615
2616 static FT_Error
2617 af_latin_hints_init( AF_GlyphHints hints,
2618 AF_StyleMetrics metrics_ ) /* AF_LatinMetrics */
2619 {
2620 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
2621
2622 FT_Render_Mode mode;
2623 FT_UInt32 scaler_flags, other_flags;
2624 FT_Face face = metrics->root.scaler.face;
2625
2626
2627 af_glyph_hints_rescale( hints, (AF_StyleMetrics)metrics );
2628
2629 /*
2630 * correct x_scale and y_scale if needed, since they may have
2631 * been modified by `af_latin_metrics_scale_dim' above
2632 */
2633 hints->x_scale = metrics->axis[AF_DIMENSION_HORZ].scale;
2634 hints->x_delta = metrics->axis[AF_DIMENSION_HORZ].delta;
2635 hints->y_scale = metrics->axis[AF_DIMENSION_VERT].scale;
2636 hints->y_delta = metrics->axis[AF_DIMENSION_VERT].delta;
2637
2638 /* compute flags depending on render mode, etc. */
2639 mode = metrics->root.scaler.render_mode;
2640
2641 scaler_flags = hints->scaler_flags;
2642 other_flags = 0;
2643
2644 /*
2645 * We snap the width of vertical stems for the monochrome and
2646 * horizontal LCD rendering targets only.
2647 */
2648 if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD )
2649 other_flags |= AF_LATIN_HINTS_HORZ_SNAP;
2650
2651 /*
2652 * We snap the width of horizontal stems for the monochrome and
2653 * vertical LCD rendering targets only.
2654 */
2655 if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V )
2656 other_flags |= AF_LATIN_HINTS_VERT_SNAP;
2657
2658 /*
2659 * We adjust stems to full pixels unless in `light' or `lcd' mode.
2660 */
2661 if ( mode != FT_RENDER_MODE_LIGHT && mode != FT_RENDER_MODE_LCD )
2662 other_flags |= AF_LATIN_HINTS_STEM_ADJUST;
2663
2664 if ( mode == FT_RENDER_MODE_MONO )
2665 other_flags |= AF_LATIN_HINTS_MONO;
2666
2667 /*
2668 * In `light' or `lcd' mode we disable horizontal hinting completely.
2669 * We also do it if the face is italic.
2670 *
2671 * However, if warping is enabled (which only works in `light' hinting
2672 * mode), advance widths get adjusted, too.
2673 */
2674 if ( mode == FT_RENDER_MODE_LIGHT || mode == FT_RENDER_MODE_LCD ||
2675 ( face->style_flags & FT_STYLE_FLAG_ITALIC ) != 0 )
2676 scaler_flags |= AF_SCALER_FLAG_NO_HORIZONTAL;
2677
2678 hints->scaler_flags = scaler_flags;
2679 hints->other_flags = other_flags;
2680
2681 return FT_Err_Ok;
2682 }
2683
2684
2685 /*************************************************************************/
2686 /*************************************************************************/
2687 /***** *****/
2688 /***** L A T I N G L Y P H G R I D - F I T T I N G *****/
2689 /***** *****/
2690 /*************************************************************************/
2691 /*************************************************************************/
2692
2693 /* Snap a given width in scaled coordinates to one of the */
2694 /* current standard widths. */
2695
2696 static FT_Pos
2697 af_latin_snap_width( AF_Width widths,
2698 FT_UInt count,
2699 FT_Pos width )
2700 {
2701 FT_UInt n;
2702 FT_Pos best = 64 + 32 + 2;
2703 FT_Pos reference = width;
2704 FT_Pos scaled;
2705
2706
2707 for ( n = 0; n < count; n++ )
2708 {
2709 FT_Pos w;
2710 FT_Pos dist;
2711
2712
2713 w = widths[n].cur;
2714 dist = width - w;
2715 if ( dist < 0 )
2716 dist = -dist;
2717 if ( dist < best )
2718 {
2719 best = dist;
2720 reference = w;
2721 }
2722 }
2723
2724 scaled = FT_PIX_ROUND( reference );
2725
2726 if ( width >= reference )
2727 {
2728 if ( width < scaled + 48 )
2729 width = reference;
2730 }
2731 else
2732 {
2733 if ( width > scaled - 48 )
2734 width = reference;
2735 }
2736
2737 return width;
2738 }
2739
2740
2741 /* Compute the snapped width of a given stem, ignoring very thin ones. */
2742 /* There is a lot of voodoo in this function; changing the hard-coded */
2743 /* parameters influence the whole hinting process. */
2744
2745 static FT_Pos
2746 af_latin_compute_stem_width( AF_GlyphHints hints,
2747 AF_Dimension dim,
2748 FT_Pos width,
2749 FT_Pos base_delta,
2750 FT_UInt base_flags,
2751 FT_UInt stem_flags )
2752 {
2753 AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics;
2754 AF_LatinAxis axis = &metrics->axis[dim];
2755 FT_Pos dist = width;
2756 FT_Int sign = 0;
2757 FT_Int vertical = ( dim == AF_DIMENSION_VERT );
2758
2759
2760 if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) ||
2761 axis->extra_light )
2762 return width;
2763
2764 if ( dist < 0 )
2765 {
2766 dist = -width;
2767 sign = 1;
2768 }
2769
2770 if ( ( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
2771 ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
2772 {
2773 /* smooth hinting process: very lightly quantize the stem width */
2774
2775 /* leave the widths of serifs alone */
2776 if ( ( stem_flags & AF_EDGE_SERIF ) &&
2777 vertical &&
2778 ( dist < 3 * 64 ) )
2779 goto Done_Width;
2780
2781 else if ( base_flags & AF_EDGE_ROUND )
2782 {
2783 if ( dist < 80 )
2784 dist = 64;
2785 }
2786 else if ( dist < 56 )
2787 dist = 56;
2788
2789 if ( axis->width_count > 0 )
2790 {
2791 FT_Pos delta;
2792
2793
2794 /* compare to standard width */
2795 delta = dist - axis->widths[0].cur;
2796
2797 if ( delta < 0 )
2798 delta = -delta;
2799
2800 if ( delta < 40 )
2801 {
2802 dist = axis->widths[0].cur;
2803 if ( dist < 48 )
2804 dist = 48;
2805
2806 goto Done_Width;
2807 }
2808
2809 if ( dist < 3 * 64 )
2810 {
2811 delta = dist & 63;
2812 dist &= -64;
2813
2814 if ( delta < 10 )
2815 dist += delta;
2816
2817 else if ( delta < 32 )
2818 dist += 10;
2819
2820 else if ( delta < 54 )
2821 dist += 54;
2822
2823 else
2824 dist += delta;
2825 }
2826 else
2827 {
2828 /* A stem's end position depends on two values: the start */
2829 /* position and the stem length. The former gets usually */
2830 /* rounded to the grid, while the latter gets rounded also if it */
2831 /* exceeds a certain length (see below in this function). This */
2832 /* `double rounding' can lead to a great difference to the */
2833 /* original, unhinted position; this normally doesn't matter for */
2834 /* large PPEM values, but for small sizes it can easily make */
2835 /* outlines collide. For this reason, we adjust the stem length */
2836 /* by a small amount depending on the PPEM value in case the */
2837 /* former and latter rounding both point into the same */
2838 /* direction. */
2839
2840 FT_Pos bdelta = 0;
2841
2842
2843 if ( ( ( width > 0 ) && ( base_delta > 0 ) ) ||
2844 ( ( width < 0 ) && ( base_delta < 0 ) ) )
2845 {
2846 FT_UInt ppem = metrics->root.scaler.face->size->metrics.x_ppem;
2847
2848
2849 if ( ppem < 10 )
2850 bdelta = base_delta;
2851 else if ( ppem < 30 )
2852 bdelta = ( base_delta * (FT_Pos)( 30 - ppem ) ) / 20;
2853
2854 if ( bdelta < 0 )
2855 bdelta = -bdelta;
2856 }
2857
2858 dist = ( dist - bdelta + 32 ) & ~63;
2859 }
2860 }
2861 }
2862 else
2863 {
2864 /* strong hinting process: snap the stem width to integer pixels */
2865
2866 FT_Pos org_dist = dist;
2867
2868
2869 dist = af_latin_snap_width( axis->widths, axis->width_count, dist );
2870
2871 if ( vertical )
2872 {
2873 /* in the case of vertical hinting, always round */
2874 /* the stem heights to integer pixels */
2875
2876 if ( dist >= 64 )
2877 dist = ( dist + 16 ) & ~63;
2878 else
2879 dist = 64;
2880 }
2881 else
2882 {
2883 if ( AF_LATIN_HINTS_DO_MONO( hints ) )
2884 {
2885 /* monochrome horizontal hinting: snap widths to integer pixels */
2886 /* with a different threshold */
2887
2888 if ( dist < 64 )
2889 dist = 64;
2890 else
2891 dist = ( dist + 32 ) & ~63;
2892 }
2893 else
2894 {
2895 /* for horizontal anti-aliased hinting, we adopt a more subtle */
2896 /* approach: we strengthen small stems, round stems whose size */
2897 /* is between 1 and 2 pixels to an integer, otherwise nothing */
2898
2899 if ( dist < 48 )
2900 dist = ( dist + 64 ) >> 1;
2901
2902 else if ( dist < 128 )
2903 {
2904 /* We only round to an integer width if the corresponding */
2905 /* distortion is less than 1/4 pixel. Otherwise this */
2906 /* makes everything worse since the diagonals, which are */
2907 /* not hinted, appear a lot bolder or thinner than the */
2908 /* vertical stems. */
2909
2910 FT_Pos delta;
2911
2912
2913 dist = ( dist + 22 ) & ~63;
2914 delta = dist - org_dist;
2915 if ( delta < 0 )
2916 delta = -delta;
2917
2918 if ( delta >= 16 )
2919 {
2920 dist = org_dist;
2921 if ( dist < 48 )
2922 dist = ( dist + 64 ) >> 1;
2923 }
2924 }
2925 else
2926 /* round otherwise to prevent color fringes in LCD mode */
2927 dist = ( dist + 32 ) & ~63;
2928 }
2929 }
2930 }
2931
2932 Done_Width:
2933 if ( sign )
2934 dist = -dist;
2935
2936 return dist;
2937 }
2938
2939
2940 /* Align one stem edge relative to the previous stem edge. */
2941
2942 static void
2943 af_latin_align_linked_edge( AF_GlyphHints hints,
2944 AF_Dimension dim,
2945 AF_Edge base_edge,
2946 AF_Edge stem_edge )
2947 {
2948 FT_Pos dist, base_delta;
2949 FT_Pos fitted_width;
2950
2951
2952 dist = stem_edge->opos - base_edge->opos;
2953 base_delta = base_edge->pos - base_edge->opos;
2954
2955 fitted_width = af_latin_compute_stem_width( hints, dim,
2956 dist, base_delta,
2957 base_edge->flags,
2958 stem_edge->flags );
2959
2960
2961 stem_edge->pos = base_edge->pos + fitted_width;
2962
2963 FT_TRACE5(( " LINK: edge %td (opos=%.2f) linked to %.2f,"
2964 " dist was %.2f, now %.2f\n",
2965 stem_edge - hints->axis[dim].edges,
2966 (double)stem_edge->opos / 64, (double)stem_edge->pos / 64,
2967 (double)dist / 64, (double)fitted_width / 64 ));
2968 }
2969
2970
2971 /* Shift the coordinates of the `serif' edge by the same amount */
2972 /* as the corresponding `base' edge has been moved already. */
2973
2974 static void
2975 af_latin_align_serif_edge( AF_GlyphHints hints,
2976 AF_Edge base,
2977 AF_Edge serif )
2978 {
2979 FT_UNUSED( hints );
2980
2981 serif->pos = base->pos + ( serif->opos - base->opos );
2982 }
2983
2984
2985 /*************************************************************************/
2986 /*************************************************************************/
2987 /*************************************************************************/
2988 /**** ****/
2989 /**** E D G E H I N T I N G ****/
2990 /**** ****/
2991 /*************************************************************************/
2992 /*************************************************************************/
2993 /*************************************************************************/
2994
2995
2996 /* The main grid-fitting routine. */
2997
2998 static void
2999 af_latin_hint_edges( AF_GlyphHints hints,
3000 AF_Dimension dim )
3001 {
3002 AF_AxisHints axis = &hints->axis[dim];
3003 AF_Edge edges = axis->edges;
3004 AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges );
3005 FT_PtrDist n_edges;
3006 AF_Edge edge;
3007 AF_Edge anchor = NULL;
3008 FT_Int has_serifs = 0;
3009
3010 AF_StyleClass style_class = hints->metrics->style_class;
3011 AF_ScriptClass script_class = af_script_classes[style_class->script];
3012
3013 FT_Bool top_to_bottom_hinting = 0;
3014
3015#ifdef FT_DEBUG_LEVEL_TRACE
3016 FT_UInt num_actions = 0;
3017#endif
3018
3019
3020 FT_TRACE5(( "latin %s edge hinting (style `%s')\n",
3021 dim == AF_DIMENSION_VERT ? "horizontal" : "vertical",
3022 af_style_names[hints->metrics->style_class->style] ));
3023
3024 if ( dim == AF_DIMENSION_VERT )
3025 top_to_bottom_hinting = script_class->top_to_bottom_hinting;
3026
3027 /* we begin by aligning all stems relative to the blue zone */
3028 /* if needed -- that's only for horizontal edges */
3029
3030 if ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_BLUES( hints ) )
3031 {
3032 for ( edge = edges; edge < edge_limit; edge++ )
3033 {
3034 AF_Width blue;
3035 AF_Edge edge1, edge2; /* these edges form the stem to check */
3036
3037
3038 if ( edge->flags & AF_EDGE_DONE )
3039 continue;
3040
3041 edge1 = NULL;
3042 edge2 = edge->link;
3043
3044 /*
3045 * If a stem contains both a neutral and a non-neutral blue zone,
3046 * skip the neutral one. Otherwise, outlines with different
3047 * directions might be incorrectly aligned at the same vertical
3048 * position.
3049 *
3050 * If we have two neutral blue zones, skip one of them.
3051 *
3052 */
3053 if ( edge->blue_edge && edge2 && edge2->blue_edge )
3054 {
3055 FT_Byte neutral = edge->flags & AF_EDGE_NEUTRAL;
3056 FT_Byte neutral2 = edge2->flags & AF_EDGE_NEUTRAL;
3057
3058
3059 if ( neutral2 )
3060 {
3061 edge2->blue_edge = NULL;
3062 edge2->flags &= ~AF_EDGE_NEUTRAL;
3063 }
3064 else if ( neutral )
3065 {
3066 edge->blue_edge = NULL;
3067 edge->flags &= ~AF_EDGE_NEUTRAL;
3068 }
3069 }
3070
3071 blue = edge->blue_edge;
3072 if ( blue )
3073 edge1 = edge;
3074
3075 /* flip edges if the other edge is aligned to a blue zone */
3076 else if ( edge2 && edge2->blue_edge )
3077 {
3078 blue = edge2->blue_edge;
3079 edge1 = edge2;
3080 edge2 = edge;
3081 }
3082
3083 if ( !edge1 )
3084 continue;
3085
3086#ifdef FT_DEBUG_LEVEL_TRACE
3087 if ( !anchor )
3088 FT_TRACE5(( " BLUE_ANCHOR: edge %td (opos=%.2f) snapped to %.2f,"
3089 " was %.2f (anchor=edge %td)\n",
3090 edge1 - edges,
3091 (double)edge1->opos / 64, (double)blue->fit / 64,
3092 (double)edge1->pos / 64, edge - edges ));
3093 else
3094 FT_TRACE5(( " BLUE: edge %td (opos=%.2f) snapped to %.2f,"
3095 " was %.2f\n",
3096 edge1 - edges,
3097 (double)edge1->opos / 64, (double)blue->fit / 64,
3098 (double)edge1->pos / 64 ));
3099
3100 num_actions++;
3101#endif
3102
3103 edge1->pos = blue->fit;
3104 edge1->flags |= AF_EDGE_DONE;
3105
3106 if ( edge2 && !edge2->blue_edge )
3107 {
3108 af_latin_align_linked_edge( hints, dim, edge1, edge2 );
3109 edge2->flags |= AF_EDGE_DONE;
3110
3111#ifdef FT_DEBUG_LEVEL_TRACE
3112 num_actions++;
3113#endif
3114 }
3115
3116 if ( !anchor )
3117 anchor = edge;
3118 }
3119 }
3120
3121 /* now we align all other stem edges, trying to maintain the */
3122 /* relative order of stems in the glyph */
3123 for ( edge = edges; edge < edge_limit; edge++ )
3124 {
3125 AF_Edge edge2;
3126
3127
3128 if ( edge->flags & AF_EDGE_DONE )
3129 continue;
3130
3131 /* skip all non-stem edges */
3132 edge2 = edge->link;
3133 if ( !edge2 )
3134 {
3135 has_serifs++;
3136 continue;
3137 }
3138
3139 /* now align the stem */
3140
3141 /* this should not happen, but it's better to be safe */
3142 if ( edge2->blue_edge )
3143 {
3144 FT_TRACE5(( " ASSERTION FAILED for edge %td\n", edge2 - edges ));
3145
3146 af_latin_align_linked_edge( hints, dim, edge2, edge );
3147 edge->flags |= AF_EDGE_DONE;
3148
3149#ifdef FT_DEBUG_LEVEL_TRACE
3150 num_actions++;
3151#endif
3152 continue;
3153 }
3154
3155 if ( !anchor )
3156 {
3157 /* if we reach this if clause, no stem has been aligned yet */
3158
3159 FT_Pos org_len, org_center, cur_len;
3160 FT_Pos cur_pos1, error1, error2, u_off, d_off;
3161
3162
3163 org_len = edge2->opos - edge->opos;
3164 cur_len = af_latin_compute_stem_width( hints, dim,
3165 org_len, 0,
3166 edge->flags,
3167 edge2->flags );
3168
3169 /* some voodoo to specially round edges for small stem widths; */
3170 /* the idea is to align the center of a stem, then shifting */
3171 /* the stem edges to suitable positions */
3172 if ( cur_len <= 64 )
3173 {
3174 /* width <= 1px */
3175 u_off = 32;
3176 d_off = 32;
3177 }
3178 else
3179 {
3180 /* 1px < width < 1.5px */
3181 u_off = 38;
3182 d_off = 26;
3183 }
3184
3185 if ( cur_len < 96 )
3186 {
3187 org_center = edge->opos + ( org_len >> 1 );
3188 cur_pos1 = FT_PIX_ROUND( org_center );
3189
3190 error1 = org_center - ( cur_pos1 - u_off );
3191 if ( error1 < 0 )
3192 error1 = -error1;
3193
3194 error2 = org_center - ( cur_pos1 + d_off );
3195 if ( error2 < 0 )
3196 error2 = -error2;
3197
3198 if ( error1 < error2 )
3199 cur_pos1 -= u_off;
3200 else
3201 cur_pos1 += d_off;
3202
3203 edge->pos = cur_pos1 - cur_len / 2;
3204 edge2->pos = edge->pos + cur_len;
3205 }
3206 else
3207 edge->pos = FT_PIX_ROUND( edge->opos );
3208
3209 anchor = edge;
3210 edge->flags |= AF_EDGE_DONE;
3211
3212 FT_TRACE5(( " ANCHOR: edge %td (opos=%.2f) and %td (opos=%.2f)"
3213 " snapped to %.2f and %.2f\n",
3214 edge - edges, (double)edge->opos / 64,
3215 edge2 - edges, (double)edge2->opos / 64,
3216 (double)edge->pos / 64, (double)edge2->pos / 64 ));
3217
3218 af_latin_align_linked_edge( hints, dim, edge, edge2 );
3219
3220#ifdef FT_DEBUG_LEVEL_TRACE
3221 num_actions += 2;
3222#endif
3223 }
3224 else
3225 {
3226 FT_Pos org_pos, org_len, org_center, cur_len;
3227 FT_Pos cur_pos1, cur_pos2, delta1, delta2;
3228
3229
3230 org_pos = anchor->pos + ( edge->opos - anchor->opos );
3231 org_len = edge2->opos - edge->opos;
3232 org_center = org_pos + ( org_len >> 1 );
3233
3234 cur_len = af_latin_compute_stem_width( hints, dim,
3235 org_len, 0,
3236 edge->flags,
3237 edge2->flags );
3238
3239 if ( edge2->flags & AF_EDGE_DONE )
3240 {
3241 FT_TRACE5(( " ADJUST: edge %td (pos=%.2f) moved to %.2f\n",
3242 edge - edges, (double)edge->pos / 64,
3243 (double)( edge2->pos - cur_len ) / 64 ));
3244
3245 edge->pos = edge2->pos - cur_len;
3246 }
3247
3248 else if ( cur_len < 96 )
3249 {
3250 FT_Pos u_off, d_off;
3251
3252
3253 cur_pos1 = FT_PIX_ROUND( org_center );
3254
3255 if ( cur_len <= 64 )
3256 {
3257 u_off = 32;
3258 d_off = 32;
3259 }
3260 else
3261 {
3262 u_off = 38;
3263 d_off = 26;
3264 }
3265
3266 delta1 = org_center - ( cur_pos1 - u_off );
3267 if ( delta1 < 0 )
3268 delta1 = -delta1;
3269
3270 delta2 = org_center - ( cur_pos1 + d_off );
3271 if ( delta2 < 0 )
3272 delta2 = -delta2;
3273
3274 if ( delta1 < delta2 )
3275 cur_pos1 -= u_off;
3276 else
3277 cur_pos1 += d_off;
3278
3279 edge->pos = cur_pos1 - cur_len / 2;
3280 edge2->pos = cur_pos1 + cur_len / 2;
3281
3282 FT_TRACE5(( " STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)"
3283 " snapped to %.2f and %.2f\n",
3284 edge - edges, (double)edge->opos / 64,
3285 edge2 - edges, (double)edge2->opos / 64,
3286 (double)edge->pos / 64, (double)edge2->pos / 64 ));
3287 }
3288
3289 else
3290 {
3291 org_pos = anchor->pos + ( edge->opos - anchor->opos );
3292 org_len = edge2->opos - edge->opos;
3293 org_center = org_pos + ( org_len >> 1 );
3294
3295 cur_len = af_latin_compute_stem_width( hints, dim,
3296 org_len, 0,
3297 edge->flags,
3298 edge2->flags );
3299
3300 cur_pos1 = FT_PIX_ROUND( org_pos );
3301 delta1 = cur_pos1 + ( cur_len >> 1 ) - org_center;
3302 if ( delta1 < 0 )
3303 delta1 = -delta1;
3304
3305 cur_pos2 = FT_PIX_ROUND( org_pos + org_len ) - cur_len;
3306 delta2 = cur_pos2 + ( cur_len >> 1 ) - org_center;
3307 if ( delta2 < 0 )
3308 delta2 = -delta2;
3309
3310 edge->pos = ( delta1 < delta2 ) ? cur_pos1 : cur_pos2;
3311 edge2->pos = edge->pos + cur_len;
3312
3313 FT_TRACE5(( " STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)"
3314 " snapped to %.2f and %.2f\n",
3315 edge - edges, (double)edge->opos / 64,
3316 edge2 - edges, (double)edge2->opos / 64,
3317 (double)edge->pos / 64, (double)edge2->pos / 64 ));
3318 }
3319
3320#ifdef FT_DEBUG_LEVEL_TRACE
3321 num_actions++;
3322#endif
3323
3324 edge->flags |= AF_EDGE_DONE;
3325 edge2->flags |= AF_EDGE_DONE;
3326
3327 if ( edge > edges &&
3328 ( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos )
3329 : ( edge->pos < edge[-1].pos ) ) )
3330 {
3331 /* don't move if stem would (almost) disappear otherwise; */
3332 /* the ad-hoc value 16 corresponds to 1/4px */
3333 if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
3334 {
3335#ifdef FT_DEBUG_LEVEL_TRACE
3336 FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n",
3337 edge - edges,
3338 (double)edge->pos / 64,
3339 (double)edge[-1].pos / 64 ));
3340
3341 num_actions++;
3342#endif
3343
3344 edge->pos = edge[-1].pos;
3345 }
3346 }
3347 }
3348 }
3349
3350 /* make sure that lowercase m's maintain their symmetry */
3351
3352 /* In general, lowercase m's have six vertical edges if they are sans */
3353 /* serif, or twelve if they are with serifs. This implementation is */
3354 /* based on that assumption, and seems to work very well with most */
3355 /* faces. However, if for a certain face this assumption is not */
3356 /* true, the m is just rendered like before. In addition, any stem */
3357 /* correction will only be applied to symmetrical glyphs (even if the */
3358 /* glyph is not an m), so the potential for unwanted distortion is */
3359 /* relatively low. */
3360
3361 /* We don't handle horizontal edges since we can't easily assure that */
3362 /* the third (lowest) stem aligns with the base line; it might end up */
3363 /* one pixel higher or lower. */
3364
3365 n_edges = edge_limit - edges;
3366 if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
3367 {
3368 AF_Edge edge1, edge2, edge3;
3369 FT_Pos dist1, dist2, span, delta;
3370
3371
3372 if ( n_edges == 6 )
3373 {
3374 edge1 = edges;
3375 edge2 = edges + 2;
3376 edge3 = edges + 4;
3377 }
3378 else
3379 {
3380 edge1 = edges + 1;
3381 edge2 = edges + 5;
3382 edge3 = edges + 9;
3383 }
3384
3385 dist1 = edge2->opos - edge1->opos;
3386 dist2 = edge3->opos - edge2->opos;
3387
3388 span = dist1 - dist2;
3389 if ( span < 0 )
3390 span = -span;
3391
3392 if ( span < 8 )
3393 {
3394 delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
3395 edge3->pos -= delta;
3396 if ( edge3->link )
3397 edge3->link->pos -= delta;
3398
3399 /* move the serifs along with the stem */
3400 if ( n_edges == 12 )
3401 {
3402 ( edges + 8 )->pos -= delta;
3403 ( edges + 11 )->pos -= delta;
3404 }
3405
3406 edge3->flags |= AF_EDGE_DONE;
3407 if ( edge3->link )
3408 edge3->link->flags |= AF_EDGE_DONE;
3409 }
3410 }
3411
3412 if ( has_serifs || !anchor )
3413 {
3414 /*
3415 * now hint the remaining edges (serifs and single) in order
3416 * to complete our processing
3417 */
3418 for ( edge = edges; edge < edge_limit; edge++ )
3419 {
3420 FT_Pos delta;
3421
3422
3423 if ( edge->flags & AF_EDGE_DONE )
3424 continue;
3425
3426 delta = 1000;
3427
3428 if ( edge->serif )
3429 {
3430 delta = edge->serif->opos - edge->opos;
3431 if ( delta < 0 )
3432 delta = -delta;
3433 }
3434
3435 if ( delta < 64 + 16 )
3436 {
3437 af_latin_align_serif_edge( hints, edge->serif, edge );
3438 FT_TRACE5(( " SERIF: edge %td (opos=%.2f) serif to %td (opos=%.2f)"
3439 " aligned to %.2f\n",
3440 edge - edges, (double)edge->opos / 64,
3441 edge->serif - edges, (double)edge->serif->opos / 64,
3442 (double)edge->pos / 64 ));
3443 }
3444 else if ( !anchor )
3445 {
3446 edge->pos = FT_PIX_ROUND( edge->opos );
3447 anchor = edge;
3448 FT_TRACE5(( " SERIF_ANCHOR: edge %td (opos=%.2f)"
3449 " snapped to %.2f\n",
3450 edge - edges,
3451 (double)edge->opos / 64, (double)edge->pos / 64 ));
3452 }
3453 else
3454 {
3455 AF_Edge before, after;
3456
3457
3458 for ( before = edge - 1; before >= edges; before-- )
3459 if ( before->flags & AF_EDGE_DONE )
3460 break;
3461
3462 for ( after = edge + 1; after < edge_limit; after++ )
3463 if ( after->flags & AF_EDGE_DONE )
3464 break;
3465
3466 if ( before >= edges && before < edge &&
3467 after < edge_limit && after > edge )
3468 {
3469 if ( after->opos == before->opos )
3470 edge->pos = before->pos;
3471 else
3472 edge->pos = before->pos +
3473 FT_MulDiv( edge->opos - before->opos,
3474 after->pos - before->pos,
3475 after->opos - before->opos );
3476
3477 FT_TRACE5(( " SERIF_LINK1: edge %td (opos=%.2f) snapped to %.2f"
3478 " from %td (opos=%.2f)\n",
3479 edge - edges, (double)edge->opos / 64,
3480 (double)edge->pos / 64,
3481 before - edges, (double)before->opos / 64 ));
3482 }
3483 else
3484 {
3485 edge->pos = anchor->pos +
3486 ( ( edge->opos - anchor->opos + 16 ) & ~31 );
3487 FT_TRACE5(( " SERIF_LINK2: edge %td (opos=%.2f)"
3488 " snapped to %.2f\n",
3489 edge - edges,
3490 (double)edge->opos / 64, (double)edge->pos / 64 ));
3491 }
3492 }
3493
3494#ifdef FT_DEBUG_LEVEL_TRACE
3495 num_actions++;
3496#endif
3497 edge->flags |= AF_EDGE_DONE;
3498
3499 if ( edge > edges &&
3500 ( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos )
3501 : ( edge->pos < edge[-1].pos ) ) )
3502 {
3503 /* don't move if stem would (almost) disappear otherwise; */
3504 /* the ad-hoc value 16 corresponds to 1/4px */
3505 if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
3506 {
3507#ifdef FT_DEBUG_LEVEL_TRACE
3508 FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n",
3509 edge - edges,
3510 (double)edge->pos / 64,
3511 (double)edge[-1].pos / 64 ));
3512
3513 num_actions++;
3514#endif
3515 edge->pos = edge[-1].pos;
3516 }
3517 }
3518
3519 if ( edge + 1 < edge_limit &&
3520 edge[1].flags & AF_EDGE_DONE &&
3521 ( top_to_bottom_hinting ? ( edge->pos < edge[1].pos )
3522 : ( edge->pos > edge[1].pos ) ) )
3523 {
3524 /* don't move if stem would (almost) disappear otherwise; */
3525 /* the ad-hoc value 16 corresponds to 1/4px */
3526 if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
3527 {
3528#ifdef FT_DEBUG_LEVEL_TRACE
3529 FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n",
3530 edge - edges,
3531 (double)edge->pos / 64,
3532 (double)edge[1].pos / 64 ));
3533
3534 num_actions++;
3535#endif
3536
3537 edge->pos = edge[1].pos;
3538 }
3539 }
3540 }
3541 }
3542
3543#ifdef FT_DEBUG_LEVEL_TRACE
3544 if ( !num_actions )
3545 FT_TRACE5(( " (none)\n" ));
3546 FT_TRACE5(( "\n" ));
3547#endif
3548 }
3549
3550
3551 /* Apply the complete hinting algorithm to a latin glyph. */
3552
3553 static FT_Error
3554 af_latin_hints_apply( FT_UInt glyph_index,
3555 AF_GlyphHints hints,
3556 FT_Outline* outline,
3557 AF_StyleMetrics metrics_ ) /* AF_LatinMetrics */
3558 {
3559 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
3560
3561 FT_Error error;
3562 int dim;
3563
3564 AF_LatinAxis axis;
3565
3566
3567 error = af_glyph_hints_reload( hints, outline );
3568 if ( error )
3569 goto Exit;
3570
3571 /* analyze glyph outline */
3572 if ( AF_HINTS_DO_HORIZONTAL( hints ) )
3573 {
3574 axis = &metrics->axis[AF_DIMENSION_HORZ];
3575 error = af_latin_hints_detect_features( hints,
3576 axis->width_count,
3577 axis->widths,
3578 AF_DIMENSION_HORZ );
3579 if ( error )
3580 goto Exit;
3581 }
3582
3583 if ( AF_HINTS_DO_VERTICAL( hints ) )
3584 {
3585 axis = &metrics->axis[AF_DIMENSION_VERT];
3586 error = af_latin_hints_detect_features( hints,
3587 axis->width_count,
3588 axis->widths,
3589 AF_DIMENSION_VERT );
3590 if ( error )
3591 goto Exit;
3592
3593 /* apply blue zones to base characters only */
3594 if ( !( metrics->root.globals->glyph_styles[glyph_index] & AF_NONBASE ) )
3595 af_latin_hints_compute_blue_edges( hints, metrics );
3596 }
3597
3598 /* grid-fit the outline */
3599 for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
3600 {
3601 if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) ||
3602 ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) ) )
3603 {
3604 af_latin_hint_edges( hints, (AF_Dimension)dim );
3605 af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim );
3606 af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
3607 af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
3608 }
3609 }
3610
3611 af_glyph_hints_save( hints, outline );
3612
3613 Exit:
3614 return error;
3615 }
3616
3617
3618 /*************************************************************************/
3619 /*************************************************************************/
3620 /***** *****/
3621 /***** L A T I N S C R I P T C L A S S *****/
3622 /***** *****/
3623 /*************************************************************************/
3624 /*************************************************************************/
3625
3626
3627 AF_DEFINE_WRITING_SYSTEM_CLASS(
3628 af_latin_writing_system_class,
3629
3630 AF_WRITING_SYSTEM_LATIN,
3631
3632 sizeof ( AF_LatinMetricsRec ),
3633
3634 (AF_WritingSystem_InitMetricsFunc) af_latin_metrics_init, /* style_metrics_init */
3635 (AF_WritingSystem_ScaleMetricsFunc)af_latin_metrics_scale, /* style_metrics_scale */
3636 (AF_WritingSystem_DoneMetricsFunc) NULL, /* style_metrics_done */
3637 (AF_WritingSystem_GetStdWidthsFunc)af_latin_get_standard_widths, /* style_metrics_getstdw */
3638
3639 (AF_WritingSystem_InitHintsFunc) af_latin_hints_init, /* style_hints_init */
3640 (AF_WritingSystem_ApplyHintsFunc) af_latin_hints_apply /* style_hints_apply */
3641 )
3642
3643
3644/* END */
3645