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