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