Home | History | Annotate | Download | only in autofit
      1 /***************************************************************************/
      2 /*                                                                         */
      3 /*  afcjk.c                                                                */
      4 /*                                                                         */
      5 /*    Auto-fitter hinting routines for CJK writing system (body).          */
      6 /*                                                                         */
      7 /*  Copyright 2006-2015 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    *  The algorithm is based on akito's autohint patch, available here:
     20    *
     21    *  http://www.kde.gr.jp/~akito/patch/freetype2/
     22    *
     23    */
     24 
     25 #include <ft2build.h>
     26 #include FT_ADVANCES_H
     27 #include FT_INTERNAL_DEBUG_H
     28 
     29 #include "afglobal.h"
     30 #include "afpic.h"
     31 #include "aflatin.h"
     32 
     33 
     34 #ifdef AF_CONFIG_OPTION_CJK
     35 
     36 #undef AF_CONFIG_OPTION_CJK_BLUE_HANI_VERT
     37 
     38 #include "afcjk.h"
     39 #include "aferrors.h"
     40 
     41 
     42 #ifdef AF_CONFIG_OPTION_USE_WARPER
     43 #include "afwarp.h"
     44 #endif
     45 
     46 
     47   /*************************************************************************/
     48   /*                                                                       */
     49   /* The macro FT_COMPONENT is used in trace mode.  It is an implicit      */
     50   /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log  */
     51   /* messages during execution.                                            */
     52   /*                                                                       */
     53 #undef  FT_COMPONENT
     54 #define FT_COMPONENT  trace_afcjk
     55 
     56 
     57   /*************************************************************************/
     58   /*************************************************************************/
     59   /*****                                                               *****/
     60   /*****              C J K   G L O B A L   M E T R I C S              *****/
     61   /*****                                                               *****/
     62   /*************************************************************************/
     63   /*************************************************************************/
     64 
     65 
     66   /* Basically the Latin version with AF_CJKMetrics */
     67   /* to replace AF_LatinMetrics.                    */
     68 
     69   FT_LOCAL_DEF( void )
     70   af_cjk_metrics_init_widths( AF_CJKMetrics  metrics,
     71                               FT_Face        face )
     72   {
     73     /* scan the array of segments in each direction */
     74     AF_GlyphHintsRec  hints[1];
     75 
     76 
     77     FT_TRACE5(( "\n"
     78                 "cjk standard widths computation (style `%s')\n"
     79                 "===================================================\n"
     80                 "\n",
     81                 af_style_names[metrics->root.style_class->style] ));
     82 
     83     af_glyph_hints_init( hints, face->memory );
     84 
     85     metrics->axis[AF_DIMENSION_HORZ].width_count = 0;
     86     metrics->axis[AF_DIMENSION_VERT].width_count = 0;
     87 
     88     {
     89       FT_Error          error;
     90       FT_ULong          glyph_index;
     91       FT_Long           y_offset;
     92       int               dim;
     93       AF_CJKMetricsRec  dummy[1];
     94       AF_Scaler         scaler = &dummy->root.scaler;
     95 
     96 #ifdef FT_CONFIG_OPTION_PIC
     97       AF_FaceGlobals  globals = metrics->root.globals;
     98 #endif
     99 
    100       AF_StyleClass   style_class  = metrics->root.style_class;
    101       AF_ScriptClass  script_class = AF_SCRIPT_CLASSES_GET
    102                                        [style_class->script];
    103 
    104       FT_UInt32  standard_char;
    105 
    106 
    107       standard_char = script_class->standard_char1;
    108       af_get_char_index( &metrics->root,
    109                          standard_char,
    110                          &glyph_index,
    111                          &y_offset );
    112       if ( !glyph_index )
    113       {
    114         if ( script_class->standard_char2 )
    115         {
    116           standard_char = script_class->standard_char2;
    117           af_get_char_index( &metrics->root,
    118                              standard_char,
    119                              &glyph_index,
    120                              &y_offset );
    121           if ( !glyph_index )
    122           {
    123             if ( script_class->standard_char3 )
    124             {
    125               standard_char = script_class->standard_char3;
    126               af_get_char_index( &metrics->root,
    127                                  standard_char,
    128                                  &glyph_index,
    129                                  &y_offset );
    130               if ( !glyph_index )
    131                 goto Exit;
    132             }
    133             else
    134               goto Exit;
    135           }
    136         }
    137         else
    138           goto Exit;
    139       }
    140 
    141       FT_TRACE5(( "standard character: U+%04lX (glyph index %d)\n",
    142                   standard_char, glyph_index ));
    143 
    144       error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
    145       if ( error || face->glyph->outline.n_points <= 0 )
    146         goto Exit;
    147 
    148       FT_ZERO( dummy );
    149 
    150       dummy->units_per_em = metrics->units_per_em;
    151 
    152       scaler->x_scale = 0x10000L;
    153       scaler->y_scale = 0x10000L;
    154       scaler->x_delta = 0;
    155       scaler->y_delta = 0;
    156 
    157       scaler->face        = face;
    158       scaler->render_mode = FT_RENDER_MODE_NORMAL;
    159       scaler->flags       = 0;
    160 
    161       af_glyph_hints_rescale( hints, (AF_StyleMetrics)dummy );
    162 
    163       error = af_glyph_hints_reload( hints, &face->glyph->outline );
    164       if ( error )
    165         goto Exit;
    166 
    167       for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
    168       {
    169         AF_CJKAxis    axis    = &metrics->axis[dim];
    170         AF_AxisHints  axhints = &hints->axis[dim];
    171         AF_Segment    seg, limit, link;
    172         FT_UInt       num_widths = 0;
    173 
    174 
    175         error = af_latin_hints_compute_segments( hints,
    176                                                  (AF_Dimension)dim );
    177         if ( error )
    178           goto Exit;
    179 
    180         af_latin_hints_link_segments( hints,
    181                                       0,
    182                                       NULL,
    183                                       (AF_Dimension)dim );
    184 
    185         seg   = axhints->segments;
    186         limit = seg + axhints->num_segments;
    187 
    188         for ( ; seg < limit; seg++ )
    189         {
    190           link = seg->link;
    191 
    192           /* we only consider stem segments there! */
    193           if ( link && link->link == seg && link > seg )
    194           {
    195             FT_Pos  dist;
    196 
    197 
    198             dist = seg->pos - link->pos;
    199             if ( dist < 0 )
    200               dist = -dist;
    201 
    202             if ( num_widths < AF_CJK_MAX_WIDTHS )
    203               axis->widths[num_widths++].org = dist;
    204           }
    205         }
    206 
    207         /* this also replaces multiple almost identical stem widths */
    208         /* with a single one (the value 100 is heuristic)           */
    209         af_sort_and_quantize_widths( &num_widths, axis->widths,
    210                                      dummy->units_per_em / 100 );
    211         axis->width_count = num_widths;
    212       }
    213 
    214     Exit:
    215       for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
    216       {
    217         AF_CJKAxis  axis = &metrics->axis[dim];
    218         FT_Pos      stdw;
    219 
    220 
    221         stdw = ( axis->width_count > 0 ) ? axis->widths[0].org
    222                                          : AF_LATIN_CONSTANT( metrics, 50 );
    223 
    224         /* let's try 20% of the smallest width */
    225         axis->edge_distance_threshold = stdw / 5;
    226         axis->standard_width          = stdw;
    227         axis->extra_light             = 0;
    228 
    229 #ifdef FT_DEBUG_LEVEL_TRACE
    230         {
    231           FT_UInt  i;
    232 
    233 
    234           FT_TRACE5(( "%s widths:\n",
    235                       dim == AF_DIMENSION_VERT ? "horizontal"
    236                                                : "vertical" ));
    237 
    238           FT_TRACE5(( "  %d (standard)", axis->standard_width ));
    239           for ( i = 1; i < axis->width_count; i++ )
    240             FT_TRACE5(( " %d", axis->widths[i].org ));
    241 
    242           FT_TRACE5(( "\n" ));
    243         }
    244 #endif
    245       }
    246     }
    247 
    248     FT_TRACE5(( "\n" ));
    249 
    250     af_glyph_hints_done( hints );
    251   }
    252 
    253 
    254   /* Find all blue zones. */
    255 
    256   static void
    257   af_cjk_metrics_init_blues( AF_CJKMetrics  metrics,
    258                              FT_Face        face )
    259   {
    260     FT_Pos      fills[AF_BLUE_STRING_MAX_LEN];
    261     FT_Pos      flats[AF_BLUE_STRING_MAX_LEN];
    262 
    263     FT_UInt     num_fills;
    264     FT_UInt     num_flats;
    265 
    266     FT_Bool     fill;
    267 
    268     AF_CJKBlue  blue;
    269     FT_Error    error;
    270     AF_CJKAxis  axis;
    271     FT_Outline  outline;
    272 
    273     AF_StyleClass  sc = metrics->root.style_class;
    274 
    275     AF_Blue_Stringset         bss = sc->blue_stringset;
    276     const AF_Blue_StringRec*  bs  = &af_blue_stringsets[bss];
    277 
    278 
    279     /* we walk over the blue character strings as specified in the   */
    280     /* style's entry in the `af_blue_stringset' array, computing its */
    281     /* extremum points (depending on the string properties)          */
    282 
    283     FT_TRACE5(( "cjk blue zones computation\n"
    284                 "==========================\n"
    285                 "\n" ));
    286 
    287     for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
    288     {
    289       const char*  p = &af_blue_strings[bs->string];
    290       FT_Pos*      blue_ref;
    291       FT_Pos*      blue_shoot;
    292 
    293 
    294       if ( AF_CJK_IS_HORIZ_BLUE( bs ) )
    295         axis = &metrics->axis[AF_DIMENSION_HORZ];
    296       else
    297         axis = &metrics->axis[AF_DIMENSION_VERT];
    298 
    299 #ifdef FT_DEBUG_LEVEL_TRACE
    300       {
    301         FT_String*  cjk_blue_name[4] =
    302         {
    303           (FT_String*)"bottom",    /* --   , --  */
    304           (FT_String*)"top",       /* --   , TOP */
    305           (FT_String*)"left",      /* HORIZ, --  */
    306           (FT_String*)"right"      /* HORIZ, TOP */
    307         };
    308 
    309 
    310         FT_TRACE5(( "blue zone %d (%s):\n",
    311                     axis->blue_count,
    312                     cjk_blue_name[AF_CJK_IS_HORIZ_BLUE( bs ) |
    313                                   AF_CJK_IS_TOP_BLUE( bs )   ] ));
    314       }
    315 #endif /* FT_DEBUG_LEVEL_TRACE */
    316 
    317       num_fills = 0;
    318       num_flats = 0;
    319 
    320       fill = 1;  /* start with characters that define fill values */
    321       FT_TRACE5(( "  [overshoot values]\n" ));
    322 
    323       while ( *p )
    324       {
    325         FT_ULong    ch;
    326         FT_ULong    glyph_index;
    327         FT_Long     y_offset;
    328         FT_Pos      best_pos;       /* same as points.y or points.x, resp. */
    329         FT_Int      best_point;
    330         FT_Vector*  points;
    331 
    332 
    333         GET_UTF8_CHAR( ch, p );
    334 
    335         /* switch to characters that define flat values */
    336         if ( ch == '|' )
    337         {
    338           fill = 0;
    339           FT_TRACE5(( "  [reference values]\n" ));
    340           continue;
    341         }
    342 
    343         /* load the character in the face -- skip unknown or empty ones */
    344         af_get_char_index( &metrics->root, ch, &glyph_index, &y_offset );
    345         if ( glyph_index == 0 )
    346         {
    347           FT_TRACE5(( "  U+%04lX unavailable\n", ch ));
    348           continue;
    349         }
    350 
    351         error   = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
    352         outline = face->glyph->outline;
    353         if ( error || outline.n_points <= 0 )
    354         {
    355           FT_TRACE5(( "  U+%04lX contains no outlines\n", ch ));
    356           continue;
    357         }
    358 
    359         /* now compute min or max point indices and coordinates */
    360         points     = outline.points;
    361         best_point = -1;
    362         best_pos   = 0;  /* make compiler happy */
    363 
    364         {
    365           FT_Int  nn;
    366           FT_Int  first = 0;
    367           FT_Int  last  = -1;
    368 
    369 
    370           for ( nn = 0; nn < outline.n_contours; first = last + 1, nn++ )
    371           {
    372             FT_Int  pp;
    373 
    374 
    375             last = outline.contours[nn];
    376 
    377             /* Avoid single-point contours since they are never rasterized. */
    378             /* In some fonts, they correspond to mark attachment points     */
    379             /* which are way outside of the glyph's real outline.           */
    380             if ( last <= first )
    381               continue;
    382 
    383             if ( AF_CJK_IS_HORIZ_BLUE( bs ) )
    384             {
    385               if ( AF_CJK_IS_RIGHT_BLUE( bs ) )
    386               {
    387                 for ( pp = first; pp <= last; pp++ )
    388                   if ( best_point < 0 || points[pp].x > best_pos )
    389                   {
    390                     best_point = pp;
    391                     best_pos   = points[pp].x;
    392                   }
    393               }
    394               else
    395               {
    396                 for ( pp = first; pp <= last; pp++ )
    397                   if ( best_point < 0 || points[pp].x < best_pos )
    398                   {
    399                     best_point = pp;
    400                     best_pos   = points[pp].x;
    401                   }
    402               }
    403             }
    404             else
    405             {
    406               if ( AF_CJK_IS_TOP_BLUE( bs ) )
    407               {
    408                 for ( pp = first; pp <= last; pp++ )
    409                   if ( best_point < 0 || points[pp].y > best_pos )
    410                   {
    411                     best_point = pp;
    412                     best_pos   = points[pp].y;
    413                   }
    414               }
    415               else
    416               {
    417                 for ( pp = first; pp <= last; pp++ )
    418                   if ( best_point < 0 || points[pp].y < best_pos )
    419                   {
    420                     best_point = pp;
    421                     best_pos   = points[pp].y;
    422                   }
    423               }
    424             }
    425           }
    426 
    427           FT_TRACE5(( "  U+%04lX: best_pos = %5ld\n", ch, best_pos ));
    428         }
    429 
    430         if ( fill )
    431           fills[num_fills++] = best_pos;
    432         else
    433           flats[num_flats++] = best_pos;
    434       }
    435 
    436       if ( num_flats == 0 && num_fills == 0 )
    437       {
    438         /*
    439          *  we couldn't find a single glyph to compute this blue zone,
    440          *  we will simply ignore it then
    441          */
    442         FT_TRACE5(( "  empty\n" ));
    443         continue;
    444       }
    445 
    446       /* we have computed the contents of the `fill' and `flats' tables,   */
    447       /* now determine the reference and overshoot position of the blue -- */
    448       /* we simply take the median value after a simple sort               */
    449       af_sort_pos( num_fills, fills );
    450       af_sort_pos( num_flats, flats );
    451 
    452       blue       = &axis->blues[axis->blue_count];
    453       blue_ref   = &blue->ref.org;
    454       blue_shoot = &blue->shoot.org;
    455 
    456       axis->blue_count++;
    457 
    458       if ( num_flats == 0 )
    459       {
    460         *blue_ref   =
    461         *blue_shoot = fills[num_fills / 2];
    462       }
    463       else if ( num_fills == 0 )
    464       {
    465         *blue_ref   =
    466         *blue_shoot = flats[num_flats / 2];
    467       }
    468       else
    469       {
    470         *blue_ref   = fills[num_fills / 2];
    471         *blue_shoot = flats[num_flats / 2];
    472       }
    473 
    474       /* make sure blue_ref >= blue_shoot for top/right or */
    475       /* vice versa for bottom/left                        */
    476       if ( *blue_shoot != *blue_ref )
    477       {
    478         FT_Pos   ref       = *blue_ref;
    479         FT_Pos   shoot     = *blue_shoot;
    480         FT_Bool  under_ref = FT_BOOL( shoot < ref );
    481 
    482 
    483         /* AF_CJK_IS_TOP_BLUE covers `right' and `top' */
    484         if ( AF_CJK_IS_TOP_BLUE( bs ) ^ under_ref )
    485         {
    486           *blue_ref   =
    487           *blue_shoot = ( shoot + ref ) / 2;
    488 
    489           FT_TRACE5(( "  [reference smaller than overshoot,"
    490                       " taking mean value]\n" ));
    491         }
    492       }
    493 
    494       blue->flags = 0;
    495       if ( AF_CJK_IS_TOP_BLUE( bs ) )
    496         blue->flags |= AF_CJK_BLUE_TOP;
    497 
    498       FT_TRACE5(( "    -> reference = %ld\n"
    499                   "       overshoot = %ld\n",
    500                   *blue_ref, *blue_shoot ));
    501     }
    502 
    503     FT_TRACE5(( "\n" ));
    504 
    505     return;
    506   }
    507 
    508 
    509   /* Basically the Latin version with type AF_CJKMetrics for metrics. */
    510 
    511   FT_LOCAL_DEF( void )
    512   af_cjk_metrics_check_digits( AF_CJKMetrics  metrics,
    513                                FT_Face        face )
    514   {
    515     FT_UInt   i;
    516     FT_Bool   started = 0, same_width = 1;
    517     FT_Fixed  advance, old_advance = 0;
    518 
    519 
    520     /* digit `0' is 0x30 in all supported charmaps */
    521     for ( i = 0x30; i <= 0x39; i++ )
    522     {
    523       FT_ULong  glyph_index;
    524       FT_Long   y_offset;
    525 
    526 
    527       af_get_char_index( &metrics->root, i, &glyph_index, &y_offset );
    528       if ( glyph_index == 0 )
    529         continue;
    530 
    531       if ( FT_Get_Advance( face, glyph_index,
    532                            FT_LOAD_NO_SCALE         |
    533                            FT_LOAD_NO_HINTING       |
    534                            FT_LOAD_IGNORE_TRANSFORM,
    535                            &advance ) )
    536         continue;
    537 
    538       if ( started )
    539       {
    540         if ( advance != old_advance )
    541         {
    542           same_width = 0;
    543           break;
    544         }
    545       }
    546       else
    547       {
    548         old_advance = advance;
    549         started     = 1;
    550       }
    551     }
    552 
    553     metrics->root.digits_have_same_width = same_width;
    554   }
    555 
    556 
    557   /* Initialize global metrics. */
    558 
    559   FT_LOCAL_DEF( FT_Error )
    560   af_cjk_metrics_init( AF_CJKMetrics  metrics,
    561                        FT_Face        face )
    562   {
    563     FT_CharMap  oldmap = face->charmap;
    564 
    565 
    566     metrics->units_per_em = face->units_per_EM;
    567 
    568     if ( !FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
    569     {
    570       af_cjk_metrics_init_widths( metrics, face );
    571       af_cjk_metrics_init_blues( metrics, face );
    572       af_cjk_metrics_check_digits( metrics, face );
    573     }
    574 
    575     FT_Set_Charmap( face, oldmap );
    576     return FT_Err_Ok;
    577   }
    578 
    579 
    580   /* Adjust scaling value, then scale and shift widths   */
    581   /* and blue zones (if applicable) for given dimension. */
    582 
    583   static void
    584   af_cjk_metrics_scale_dim( AF_CJKMetrics  metrics,
    585                             AF_Scaler      scaler,
    586                             AF_Dimension   dim )
    587   {
    588     FT_Fixed    scale;
    589     FT_Pos      delta;
    590     AF_CJKAxis  axis;
    591     FT_UInt     nn;
    592 
    593 
    594     if ( dim == AF_DIMENSION_HORZ )
    595     {
    596       scale = scaler->x_scale;
    597       delta = scaler->x_delta;
    598     }
    599     else
    600     {
    601       scale = scaler->y_scale;
    602       delta = scaler->y_delta;
    603     }
    604 
    605     axis = &metrics->axis[dim];
    606 
    607     if ( axis->org_scale == scale && axis->org_delta == delta )
    608       return;
    609 
    610     axis->org_scale = scale;
    611     axis->org_delta = delta;
    612 
    613     axis->scale = scale;
    614     axis->delta = delta;
    615 
    616     /* scale the blue zones */
    617     for ( nn = 0; nn < axis->blue_count; nn++ )
    618     {
    619       AF_CJKBlue  blue = &axis->blues[nn];
    620       FT_Pos      dist;
    621 
    622 
    623       blue->ref.cur   = FT_MulFix( blue->ref.org, scale ) + delta;
    624       blue->ref.fit   = blue->ref.cur;
    625       blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta;
    626       blue->shoot.fit = blue->shoot.cur;
    627       blue->flags    &= ~AF_CJK_BLUE_ACTIVE;
    628 
    629       /* a blue zone is only active if it is less than 3/4 pixels tall */
    630       dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale );
    631       if ( dist <= 48 && dist >= -48 )
    632       {
    633         FT_Pos  delta1, delta2;
    634 
    635 
    636         blue->ref.fit  = FT_PIX_ROUND( blue->ref.cur );
    637 
    638         /* shoot is under shoot for cjk */
    639         delta1 = FT_DivFix( blue->ref.fit, scale ) - blue->shoot.org;
    640         delta2 = delta1;
    641         if ( delta1 < 0 )
    642           delta2 = -delta2;
    643 
    644         delta2 = FT_MulFix( delta2, scale );
    645 
    646         FT_TRACE5(( "delta: %d", delta1 ));
    647         if ( delta2 < 32 )
    648           delta2 = 0;
    649 #if 0
    650         else if ( delta2 < 64 )
    651           delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & ~31 );
    652 #endif
    653         else
    654           delta2 = FT_PIX_ROUND( delta2 );
    655         FT_TRACE5(( "/%d\n", delta2 ));
    656 
    657         if ( delta1 < 0 )
    658           delta2 = -delta2;
    659 
    660         blue->shoot.fit = blue->ref.fit - delta2;
    661 
    662         FT_TRACE5(( ">> active cjk blue zone %c%d[%ld/%ld]:\n"
    663                     "     ref:   cur=%.2f fit=%.2f\n"
    664                     "     shoot: cur=%.2f fit=%.2f\n",
    665                     ( dim == AF_DIMENSION_HORZ ) ? 'H' : 'V',
    666                     nn, blue->ref.org, blue->shoot.org,
    667                     blue->ref.cur / 64.0, blue->ref.fit / 64.0,
    668                     blue->shoot.cur / 64.0, blue->shoot.fit / 64.0 ));
    669 
    670         blue->flags |= AF_CJK_BLUE_ACTIVE;
    671       }
    672     }
    673   }
    674 
    675 
    676   /* Scale global values in both directions. */
    677 
    678   FT_LOCAL_DEF( void )
    679   af_cjk_metrics_scale( AF_CJKMetrics  metrics,
    680                         AF_Scaler      scaler )
    681   {
    682     /* we copy the whole structure since the x and y scaling values */
    683     /* are not modified, contrary to e.g. the `latin' auto-hinter   */
    684     metrics->root.scaler = *scaler;
    685 
    686     af_cjk_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
    687     af_cjk_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
    688   }
    689 
    690 
    691   /*************************************************************************/
    692   /*************************************************************************/
    693   /*****                                                               *****/
    694   /*****              C J K   G L Y P H   A N A L Y S I S              *****/
    695   /*****                                                               *****/
    696   /*************************************************************************/
    697   /*************************************************************************/
    698 
    699 
    700   /* Walk over all contours and compute its segments. */
    701 
    702   static FT_Error
    703   af_cjk_hints_compute_segments( AF_GlyphHints  hints,
    704                                  AF_Dimension   dim )
    705   {
    706     AF_AxisHints  axis          = &hints->axis[dim];
    707     AF_Segment    segments      = axis->segments;
    708     AF_Segment    segment_limit = segments + axis->num_segments;
    709     FT_Error      error;
    710     AF_Segment    seg;
    711 
    712 
    713     error = af_latin_hints_compute_segments( hints, dim );
    714     if ( error )
    715       return error;
    716 
    717     /* a segment is round if it doesn't have successive */
    718     /* on-curve points.                                 */
    719     for ( seg = segments; seg < segment_limit; seg++ )
    720     {
    721       AF_Point  pt   = seg->first;
    722       AF_Point  last = seg->last;
    723       FT_UInt   f0   = pt->flags & AF_FLAG_CONTROL;
    724       FT_UInt   f1;
    725 
    726 
    727       seg->flags &= ~AF_EDGE_ROUND;
    728 
    729       for ( ; pt != last; f0 = f1 )
    730       {
    731         pt = pt->next;
    732         f1 = pt->flags & AF_FLAG_CONTROL;
    733 
    734         if ( !f0 && !f1 )
    735           break;
    736 
    737         if ( pt == last )
    738           seg->flags |= AF_EDGE_ROUND;
    739       }
    740     }
    741 
    742     return FT_Err_Ok;
    743   }
    744 
    745 
    746   static void
    747   af_cjk_hints_link_segments( AF_GlyphHints  hints,
    748                               AF_Dimension   dim )
    749   {
    750     AF_AxisHints  axis          = &hints->axis[dim];
    751     AF_Segment    segments      = axis->segments;
    752     AF_Segment    segment_limit = segments + axis->num_segments;
    753     AF_Direction  major_dir     = axis->major_dir;
    754     AF_Segment    seg1, seg2;
    755     FT_Pos        len_threshold;
    756     FT_Pos        dist_threshold;
    757 
    758 
    759     len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 );
    760 
    761     dist_threshold = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
    762                                                   : hints->y_scale;
    763     dist_threshold = FT_DivFix( 64 * 3, dist_threshold );
    764 
    765     /* now compare each segment to the others */
    766     for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    767     {
    768       if ( seg1->dir != major_dir )
    769         continue;
    770 
    771       for ( seg2 = segments; seg2 < segment_limit; seg2++ )
    772         if ( seg2 != seg1 && seg1->dir + seg2->dir == 0 )
    773         {
    774           FT_Pos  dist = seg2->pos - seg1->pos;
    775 
    776 
    777           if ( dist < 0 )
    778             continue;
    779 
    780           {
    781             FT_Pos  min = seg1->min_coord;
    782             FT_Pos  max = seg1->max_coord;
    783             FT_Pos  len;
    784 
    785 
    786             if ( min < seg2->min_coord )
    787               min = seg2->min_coord;
    788 
    789             if ( max > seg2->max_coord )
    790               max = seg2->max_coord;
    791 
    792             len = max - min;
    793             if ( len >= len_threshold )
    794             {
    795               if ( dist * 8 < seg1->score * 9                        &&
    796                    ( dist * 8 < seg1->score * 7 || seg1->len < len ) )
    797               {
    798                 seg1->score = dist;
    799                 seg1->len   = len;
    800                 seg1->link  = seg2;
    801               }
    802 
    803               if ( dist * 8 < seg2->score * 9                        &&
    804                    ( dist * 8 < seg2->score * 7 || seg2->len < len ) )
    805               {
    806                 seg2->score = dist;
    807                 seg2->len   = len;
    808                 seg2->link  = seg1;
    809               }
    810             }
    811           }
    812         }
    813     }
    814 
    815     /*
    816      *  now compute the `serif' segments
    817      *
    818      *  In Hanzi, some strokes are wider on one or both of the ends.
    819      *  We either identify the stems on the ends as serifs or remove
    820      *  the linkage, depending on the length of the stems.
    821      *
    822      */
    823 
    824     {
    825       AF_Segment  link1, link2;
    826 
    827 
    828       for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    829       {
    830         link1 = seg1->link;
    831         if ( !link1 || link1->link != seg1 || link1->pos <= seg1->pos )
    832           continue;
    833 
    834         if ( seg1->score >= dist_threshold )
    835           continue;
    836 
    837         for ( seg2 = segments; seg2 < segment_limit; seg2++ )
    838         {
    839           if ( seg2->pos > seg1->pos || seg1 == seg2 )
    840             continue;
    841 
    842           link2 = seg2->link;
    843           if ( !link2 || link2->link != seg2 || link2->pos < link1->pos )
    844             continue;
    845 
    846           if ( seg1->pos == seg2->pos && link1->pos == link2->pos )
    847             continue;
    848 
    849           if ( seg2->score <= seg1->score || seg1->score * 4 <= seg2->score )
    850             continue;
    851 
    852           /* seg2 < seg1 < link1 < link2 */
    853 
    854           if ( seg1->len >= seg2->len * 3 )
    855           {
    856             AF_Segment  seg;
    857 
    858 
    859             for ( seg = segments; seg < segment_limit; seg++ )
    860             {
    861               AF_Segment  link = seg->link;
    862 
    863 
    864               if ( link == seg2 )
    865               {
    866                 seg->link  = NULL;
    867                 seg->serif = link1;
    868               }
    869               else if ( link == link2 )
    870               {
    871                 seg->link  = NULL;
    872                 seg->serif = seg1;
    873               }
    874             }
    875           }
    876           else
    877           {
    878             seg1->link = link1->link = NULL;
    879 
    880             break;
    881           }
    882         }
    883       }
    884     }
    885 
    886     for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    887     {
    888       seg2 = seg1->link;
    889 
    890       if ( seg2 )
    891       {
    892         seg2->num_linked++;
    893         if ( seg2->link != seg1 )
    894         {
    895           seg1->link = NULL;
    896 
    897           if ( seg2->score < dist_threshold || seg1->score < seg2->score * 4 )
    898             seg1->serif = seg2->link;
    899           else
    900             seg2->num_linked--;
    901         }
    902       }
    903     }
    904   }
    905 
    906 
    907   static FT_Error
    908   af_cjk_hints_compute_edges( AF_GlyphHints  hints,
    909                               AF_Dimension   dim )
    910   {
    911     AF_AxisHints  axis   = &hints->axis[dim];
    912     FT_Error      error  = FT_Err_Ok;
    913     FT_Memory     memory = hints->memory;
    914     AF_CJKAxis    laxis  = &((AF_CJKMetrics)hints->metrics)->axis[dim];
    915 
    916     AF_Segment    segments      = axis->segments;
    917     AF_Segment    segment_limit = segments + axis->num_segments;
    918     AF_Segment    seg;
    919 
    920     FT_Fixed      scale;
    921     FT_Pos        edge_distance_threshold;
    922 
    923 
    924     axis->num_edges = 0;
    925 
    926     scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
    927                                          : hints->y_scale;
    928 
    929     /*********************************************************************/
    930     /*                                                                   */
    931     /* We begin by generating a sorted table of edges for the current    */
    932     /* direction.  To do so, we simply scan each segment and try to find */
    933     /* an edge in our table that corresponds to its position.            */
    934     /*                                                                   */
    935     /* If no edge is found, we create and insert a new edge in the       */
    936     /* sorted table.  Otherwise, we simply add the segment to the edge's */
    937     /* list which is then processed in the second step to compute the    */
    938     /* edge's properties.                                                */
    939     /*                                                                   */
    940     /* Note that the edges table is sorted along the segment/edge        */
    941     /* position.                                                         */
    942     /*                                                                   */
    943     /*********************************************************************/
    944 
    945     edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
    946                                          scale );
    947     if ( edge_distance_threshold > 64 / 4 )
    948       edge_distance_threshold = FT_DivFix( 64 / 4, scale );
    949     else
    950       edge_distance_threshold = laxis->edge_distance_threshold;
    951 
    952     for ( seg = segments; seg < segment_limit; seg++ )
    953     {
    954       AF_Edge  found = NULL;
    955       FT_Pos   best  = 0xFFFFU;
    956       FT_Int   ee;
    957 
    958 
    959       /* look for an edge corresponding to the segment */
    960       for ( ee = 0; ee < axis->num_edges; ee++ )
    961       {
    962         AF_Edge  edge = axis->edges + ee;
    963         FT_Pos   dist;
    964 
    965 
    966         if ( edge->dir != seg->dir )
    967           continue;
    968 
    969         dist = seg->pos - edge->fpos;
    970         if ( dist < 0 )
    971           dist = -dist;
    972 
    973         if ( dist < edge_distance_threshold && dist < best )
    974         {
    975           AF_Segment  link = seg->link;
    976 
    977 
    978           /* check whether all linked segments of the candidate edge */
    979           /* can make a single edge.                                 */
    980           if ( link )
    981           {
    982             AF_Segment  seg1  = edge->first;
    983             FT_Pos      dist2 = 0;
    984 
    985 
    986             do
    987             {
    988               AF_Segment  link1 = seg1->link;
    989 
    990 
    991               if ( link1 )
    992               {
    993                 dist2 = AF_SEGMENT_DIST( link, link1 );
    994                 if ( dist2 >= edge_distance_threshold )
    995                   break;
    996               }
    997 
    998             } while ( ( seg1 = seg1->edge_next ) != edge->first );
    999 
   1000             if ( dist2 >= edge_distance_threshold )
   1001               continue;
   1002           }
   1003 
   1004           best  = dist;
   1005           found = edge;
   1006         }
   1007       }
   1008 
   1009       if ( !found )
   1010       {
   1011         AF_Edge  edge;
   1012 
   1013 
   1014         /* insert a new edge in the list and */
   1015         /* sort according to the position    */
   1016         error = af_axis_hints_new_edge( axis, seg->pos,
   1017                                         (AF_Direction)seg->dir,
   1018                                         memory, &edge );
   1019         if ( error )
   1020           goto Exit;
   1021 
   1022         /* add the segment to the new edge's list */
   1023         FT_ZERO( edge );
   1024 
   1025         edge->first    = seg;
   1026         edge->last     = seg;
   1027         edge->dir      = seg->dir;
   1028         edge->fpos     = seg->pos;
   1029         edge->opos     = FT_MulFix( seg->pos, scale );
   1030         edge->pos      = edge->opos;
   1031         seg->edge_next = seg;
   1032       }
   1033       else
   1034       {
   1035         /* if an edge was found, simply add the segment to the edge's */
   1036         /* list                                                       */
   1037         seg->edge_next         = found->first;
   1038         found->last->edge_next = seg;
   1039         found->last            = seg;
   1040       }
   1041     }
   1042 
   1043     /******************************************************************/
   1044     /*                                                                */
   1045     /* Good, we now compute each edge's properties according to the   */
   1046     /* segments found on its position.  Basically, these are          */
   1047     /*                                                                */
   1048     /*  - the edge's main direction                                   */
   1049     /*  - stem edge, serif edge or both (which defaults to stem then) */
   1050     /*  - rounded edge, straight or both (which defaults to straight) */
   1051     /*  - link for edge                                               */
   1052     /*                                                                */
   1053     /******************************************************************/
   1054 
   1055     /* first of all, set the `edge' field in each segment -- this is */
   1056     /* required in order to compute edge links                       */
   1057 
   1058     /*
   1059      * Note that removing this loop and setting the `edge' field of each
   1060      * segment directly in the code above slows down execution speed for
   1061      * some reasons on platforms like the Sun.
   1062      */
   1063     {
   1064       AF_Edge  edges      = axis->edges;
   1065       AF_Edge  edge_limit = edges + axis->num_edges;
   1066       AF_Edge  edge;
   1067 
   1068 
   1069       for ( edge = edges; edge < edge_limit; edge++ )
   1070       {
   1071         seg = edge->first;
   1072         if ( seg )
   1073           do
   1074           {
   1075             seg->edge = edge;
   1076             seg       = seg->edge_next;
   1077 
   1078           } while ( seg != edge->first );
   1079       }
   1080 
   1081       /* now compute each edge properties */
   1082       for ( edge = edges; edge < edge_limit; edge++ )
   1083       {
   1084         FT_Int  is_round    = 0;  /* does it contain round segments?    */
   1085         FT_Int  is_straight = 0;  /* does it contain straight segments? */
   1086 
   1087 
   1088         seg = edge->first;
   1089 
   1090         do
   1091         {
   1092           FT_Bool  is_serif;
   1093 
   1094 
   1095           /* check for roundness of segment */
   1096           if ( seg->flags & AF_EDGE_ROUND )
   1097             is_round++;
   1098           else
   1099             is_straight++;
   1100 
   1101           /* check for links -- if seg->serif is set, then seg->link must */
   1102           /* be ignored                                                   */
   1103           is_serif = (FT_Bool)( seg->serif && seg->serif->edge != edge );
   1104 
   1105           if ( seg->link || is_serif )
   1106           {
   1107             AF_Edge     edge2;
   1108             AF_Segment  seg2;
   1109 
   1110 
   1111             edge2 = edge->link;
   1112             seg2  = seg->link;
   1113 
   1114             if ( is_serif )
   1115             {
   1116               seg2  = seg->serif;
   1117               edge2 = edge->serif;
   1118             }
   1119 
   1120             if ( edge2 )
   1121             {
   1122               FT_Pos  edge_delta;
   1123               FT_Pos  seg_delta;
   1124 
   1125 
   1126               edge_delta = edge->fpos - edge2->fpos;
   1127               if ( edge_delta < 0 )
   1128                 edge_delta = -edge_delta;
   1129 
   1130               seg_delta = AF_SEGMENT_DIST( seg, seg2 );
   1131 
   1132               if ( seg_delta < edge_delta )
   1133                 edge2 = seg2->edge;
   1134             }
   1135             else
   1136               edge2 = seg2->edge;
   1137 
   1138             if ( is_serif )
   1139             {
   1140               edge->serif   = edge2;
   1141               edge2->flags |= AF_EDGE_SERIF;
   1142             }
   1143             else
   1144               edge->link  = edge2;
   1145           }
   1146 
   1147           seg = seg->edge_next;
   1148 
   1149         } while ( seg != edge->first );
   1150 
   1151         /* set the round/straight flags */
   1152         edge->flags = AF_EDGE_NORMAL;
   1153 
   1154         if ( is_round > 0 && is_round >= is_straight )
   1155           edge->flags |= AF_EDGE_ROUND;
   1156 
   1157         /* get rid of serifs if link is set                 */
   1158         /* XXX: This gets rid of many unpleasant artefacts! */
   1159         /*      Example: the `c' in cour.pfa at size 13     */
   1160 
   1161         if ( edge->serif && edge->link )
   1162           edge->serif = NULL;
   1163       }
   1164     }
   1165 
   1166   Exit:
   1167     return error;
   1168   }
   1169 
   1170 
   1171   /* Detect segments and edges for given dimension. */
   1172 
   1173   static FT_Error
   1174   af_cjk_hints_detect_features( AF_GlyphHints  hints,
   1175                                 AF_Dimension   dim )
   1176   {
   1177     FT_Error  error;
   1178 
   1179 
   1180     error = af_cjk_hints_compute_segments( hints, dim );
   1181     if ( !error )
   1182     {
   1183       af_cjk_hints_link_segments( hints, dim );
   1184 
   1185       error = af_cjk_hints_compute_edges( hints, dim );
   1186     }
   1187     return error;
   1188   }
   1189 
   1190 
   1191   /* Compute all edges which lie within blue zones. */
   1192 
   1193   static void
   1194   af_cjk_hints_compute_blue_edges( AF_GlyphHints  hints,
   1195                                    AF_CJKMetrics  metrics,
   1196                                    AF_Dimension   dim )
   1197   {
   1198     AF_AxisHints  axis       = &hints->axis[dim];
   1199     AF_Edge       edge       = axis->edges;
   1200     AF_Edge       edge_limit = edge + axis->num_edges;
   1201     AF_CJKAxis    cjk        = &metrics->axis[dim];
   1202     FT_Fixed      scale      = cjk->scale;
   1203     FT_Pos        best_dist0;  /* initial threshold */
   1204 
   1205 
   1206     /* compute the initial threshold as a fraction of the EM size */
   1207     best_dist0 = FT_MulFix( metrics->units_per_em / 40, scale );
   1208 
   1209     if ( best_dist0 > 64 / 2 ) /* maximum 1/2 pixel */
   1210       best_dist0 = 64 / 2;
   1211 
   1212     /* compute which blue zones are active, i.e. have their scaled */
   1213     /* size < 3/4 pixels                                           */
   1214 
   1215     /* If the distant between an edge and a blue zone is shorter than */
   1216     /* best_dist0, set the blue zone for the edge.  Then search for   */
   1217     /* the blue zone with the smallest best_dist to the edge.         */
   1218 
   1219     for ( ; edge < edge_limit; edge++ )
   1220     {
   1221       FT_UInt   bb;
   1222       AF_Width  best_blue = NULL;
   1223       FT_Pos    best_dist = best_dist0;
   1224 
   1225 
   1226       for ( bb = 0; bb < cjk->blue_count; bb++ )
   1227       {
   1228         AF_CJKBlue  blue = cjk->blues + bb;
   1229         FT_Bool     is_top_right_blue, is_major_dir;
   1230 
   1231 
   1232         /* skip inactive blue zones (i.e., those that are too small) */
   1233         if ( !( blue->flags & AF_CJK_BLUE_ACTIVE ) )
   1234           continue;
   1235 
   1236         /* if it is a top zone, check for right edges -- if it is a bottom */
   1237         /* zone, check for left edges                                      */
   1238         /*                                                                 */
   1239         /* of course, that's for TrueType                                  */
   1240         is_top_right_blue =
   1241           (FT_Byte)( ( blue->flags & AF_CJK_BLUE_TOP ) != 0 );
   1242         is_major_dir =
   1243           FT_BOOL( edge->dir == axis->major_dir );
   1244 
   1245         /* if it is a top zone, the edge must be against the major    */
   1246         /* direction; if it is a bottom zone, it must be in the major */
   1247         /* direction                                                  */
   1248         if ( is_top_right_blue ^ is_major_dir )
   1249         {
   1250           FT_Pos    dist;
   1251           AF_Width  compare;
   1252 
   1253 
   1254           /* Compare the edge to the closest blue zone type */
   1255           if ( FT_ABS( edge->fpos - blue->ref.org ) >
   1256                FT_ABS( edge->fpos - blue->shoot.org ) )
   1257             compare = &blue->shoot;
   1258           else
   1259             compare = &blue->ref;
   1260 
   1261           dist = edge->fpos - compare->org;
   1262           if ( dist < 0 )
   1263             dist = -dist;
   1264 
   1265           dist = FT_MulFix( dist, scale );
   1266           if ( dist < best_dist )
   1267           {
   1268             best_dist = dist;
   1269             best_blue = compare;
   1270           }
   1271         }
   1272       }
   1273 
   1274       if ( best_blue )
   1275         edge->blue_edge = best_blue;
   1276     }
   1277   }
   1278 
   1279 
   1280   /* Initalize hinting engine. */
   1281 
   1282   FT_LOCAL_DEF( FT_Error )
   1283   af_cjk_hints_init( AF_GlyphHints  hints,
   1284                      AF_CJKMetrics  metrics )
   1285   {
   1286     FT_Render_Mode  mode;
   1287     FT_UInt32       scaler_flags, other_flags;
   1288 
   1289 
   1290     af_glyph_hints_rescale( hints, (AF_StyleMetrics)metrics );
   1291 
   1292     /*
   1293      *  correct x_scale and y_scale when needed, since they may have
   1294      *  been modified af_cjk_scale_dim above
   1295      */
   1296     hints->x_scale = metrics->axis[AF_DIMENSION_HORZ].scale;
   1297     hints->x_delta = metrics->axis[AF_DIMENSION_HORZ].delta;
   1298     hints->y_scale = metrics->axis[AF_DIMENSION_VERT].scale;
   1299     hints->y_delta = metrics->axis[AF_DIMENSION_VERT].delta;
   1300 
   1301     /* compute flags depending on render mode, etc. */
   1302     mode = metrics->root.scaler.render_mode;
   1303 
   1304 #if 0 /* AF_CONFIG_OPTION_USE_WARPER */
   1305     if ( mode == FT_RENDER_MODE_LCD || mode == FT_RENDER_MODE_LCD_V )
   1306       metrics->root.scaler.render_mode = mode = FT_RENDER_MODE_NORMAL;
   1307 #endif
   1308 
   1309     scaler_flags = hints->scaler_flags;
   1310     other_flags  = 0;
   1311 
   1312     /*
   1313      *  We snap the width of vertical stems for the monochrome and
   1314      *  horizontal LCD rendering targets only.
   1315      */
   1316     if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD )
   1317       other_flags |= AF_LATIN_HINTS_HORZ_SNAP;
   1318 
   1319     /*
   1320      *  We snap the width of horizontal stems for the monochrome and
   1321      *  vertical LCD rendering targets only.
   1322      */
   1323     if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V )
   1324       other_flags |= AF_LATIN_HINTS_VERT_SNAP;
   1325 
   1326     /*
   1327      *  We adjust stems to full pixels only if we don't use the `light' mode.
   1328      */
   1329     if ( mode != FT_RENDER_MODE_LIGHT )
   1330       other_flags |= AF_LATIN_HINTS_STEM_ADJUST;
   1331 
   1332     if ( mode == FT_RENDER_MODE_MONO )
   1333       other_flags |= AF_LATIN_HINTS_MONO;
   1334 
   1335     scaler_flags |= AF_SCALER_FLAG_NO_ADVANCE;
   1336 
   1337 #ifdef AF_CONFIG_OPTION_USE_WARPER
   1338     /* get (global) warper flag */
   1339     if ( !metrics->root.globals->module->warping )
   1340       scaler_flags |= AF_SCALER_FLAG_NO_WARPER;
   1341 #endif
   1342 
   1343     hints->scaler_flags = scaler_flags;
   1344     hints->other_flags  = other_flags;
   1345 
   1346     return FT_Err_Ok;
   1347   }
   1348 
   1349 
   1350   /*************************************************************************/
   1351   /*************************************************************************/
   1352   /*****                                                               *****/
   1353   /*****          C J K   G L Y P H   G R I D - F I T T I N G          *****/
   1354   /*****                                                               *****/
   1355   /*************************************************************************/
   1356   /*************************************************************************/
   1357 
   1358   /* Snap a given width in scaled coordinates to one of the */
   1359   /* current standard widths.                               */
   1360 
   1361   static FT_Pos
   1362   af_cjk_snap_width( AF_Width  widths,
   1363                      FT_UInt   count,
   1364                      FT_Pos    width )
   1365   {
   1366     FT_UInt  n;
   1367     FT_Pos   best      = 64 + 32 + 2;
   1368     FT_Pos   reference = width;
   1369     FT_Pos   scaled;
   1370 
   1371 
   1372     for ( n = 0; n < count; n++ )
   1373     {
   1374       FT_Pos  w;
   1375       FT_Pos  dist;
   1376 
   1377 
   1378       w = widths[n].cur;
   1379       dist = width - w;
   1380       if ( dist < 0 )
   1381         dist = -dist;
   1382       if ( dist < best )
   1383       {
   1384         best      = dist;
   1385         reference = w;
   1386       }
   1387     }
   1388 
   1389     scaled = FT_PIX_ROUND( reference );
   1390 
   1391     if ( width >= reference )
   1392     {
   1393       if ( width < scaled + 48 )
   1394         width = reference;
   1395     }
   1396     else
   1397     {
   1398       if ( width > scaled - 48 )
   1399         width = reference;
   1400     }
   1401 
   1402     return width;
   1403   }
   1404 
   1405 
   1406   /* Compute the snapped width of a given stem.                          */
   1407   /* There is a lot of voodoo in this function; changing the hard-coded  */
   1408   /* parameters influence the whole hinting process.                     */
   1409 
   1410   static FT_Pos
   1411   af_cjk_compute_stem_width( AF_GlyphHints  hints,
   1412                              AF_Dimension   dim,
   1413                              FT_Pos         width,
   1414                              FT_UInt        base_flags,
   1415                              FT_UInt        stem_flags )
   1416   {
   1417     AF_CJKMetrics  metrics  = (AF_CJKMetrics)hints->metrics;
   1418     AF_CJKAxis     axis     = &metrics->axis[dim];
   1419     FT_Pos         dist     = width;
   1420     FT_Int         sign     = 0;
   1421     FT_Bool        vertical = FT_BOOL( dim == AF_DIMENSION_VERT );
   1422 
   1423     FT_UNUSED( base_flags );
   1424     FT_UNUSED( stem_flags );
   1425 
   1426 
   1427     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
   1428       return width;
   1429 
   1430     if ( dist < 0 )
   1431     {
   1432       dist = -width;
   1433       sign = 1;
   1434     }
   1435 
   1436     if ( (  vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
   1437          ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
   1438     {
   1439       /* smooth hinting process: very lightly quantize the stem width */
   1440 
   1441       if ( axis->width_count > 0 )
   1442       {
   1443         if ( FT_ABS( dist - axis->widths[0].cur ) < 40 )
   1444         {
   1445           dist = axis->widths[0].cur;
   1446           if ( dist < 48 )
   1447             dist = 48;
   1448 
   1449           goto Done_Width;
   1450         }
   1451       }
   1452 
   1453       if ( dist < 54 )
   1454         dist += ( 54 - dist ) / 2 ;
   1455       else if ( dist < 3 * 64 )
   1456       {
   1457         FT_Pos  delta;
   1458 
   1459 
   1460         delta  = dist & 63;
   1461         dist  &= -64;
   1462 
   1463         if ( delta < 10 )
   1464           dist += delta;
   1465         else if ( delta < 22 )
   1466           dist += 10;
   1467         else if ( delta < 42 )
   1468           dist += delta;
   1469         else if ( delta < 54 )
   1470           dist += 54;
   1471         else
   1472           dist += delta;
   1473       }
   1474     }
   1475     else
   1476     {
   1477       /* strong hinting process: snap the stem width to integer pixels */
   1478 
   1479       dist = af_cjk_snap_width( axis->widths, axis->width_count, dist );
   1480 
   1481       if ( vertical )
   1482       {
   1483         /* in the case of vertical hinting, always round */
   1484         /* the stem heights to integer pixels            */
   1485 
   1486         if ( dist >= 64 )
   1487           dist = ( dist + 16 ) & ~63;
   1488         else
   1489           dist = 64;
   1490       }
   1491       else
   1492       {
   1493         if ( AF_LATIN_HINTS_DO_MONO( hints ) )
   1494         {
   1495           /* monochrome horizontal hinting: snap widths to integer pixels */
   1496           /* with a different threshold                                   */
   1497 
   1498           if ( dist < 64 )
   1499             dist = 64;
   1500           else
   1501             dist = ( dist + 32 ) & ~63;
   1502         }
   1503         else
   1504         {
   1505           /* for horizontal anti-aliased hinting, we adopt a more subtle */
   1506           /* approach: we strengthen small stems, round stems whose size */
   1507           /* is between 1 and 2 pixels to an integer, otherwise nothing  */
   1508 
   1509           if ( dist < 48 )
   1510             dist = ( dist + 64 ) >> 1;
   1511 
   1512           else if ( dist < 128 )
   1513             dist = ( dist + 22 ) & ~63;
   1514           else
   1515             /* round otherwise to prevent color fringes in LCD mode */
   1516             dist = ( dist + 32 ) & ~63;
   1517         }
   1518       }
   1519     }
   1520 
   1521   Done_Width:
   1522     if ( sign )
   1523       dist = -dist;
   1524 
   1525     return dist;
   1526   }
   1527 
   1528 
   1529   /* Align one stem edge relative to the previous stem edge. */
   1530 
   1531   static void
   1532   af_cjk_align_linked_edge( AF_GlyphHints  hints,
   1533                             AF_Dimension   dim,
   1534                             AF_Edge        base_edge,
   1535                             AF_Edge        stem_edge )
   1536   {
   1537     FT_Pos  dist = stem_edge->opos - base_edge->opos;
   1538 
   1539     FT_Pos  fitted_width = af_cjk_compute_stem_width( hints, dim, dist,
   1540                                                       base_edge->flags,
   1541                                                       stem_edge->flags );
   1542 
   1543 
   1544     stem_edge->pos = base_edge->pos + fitted_width;
   1545 
   1546     FT_TRACE5(( "  CJKLINK: edge %d @%d (opos=%.2f) linked to %.2f,"
   1547                 " dist was %.2f, now %.2f\n",
   1548                 stem_edge - hints->axis[dim].edges, stem_edge->fpos,
   1549                 stem_edge->opos / 64.0, stem_edge->pos / 64.0,
   1550                 dist / 64.0, fitted_width / 64.0 ));
   1551   }
   1552 
   1553 
   1554   /* Shift the coordinates of the `serif' edge by the same amount */
   1555   /* as the corresponding `base' edge has been moved already.     */
   1556 
   1557   static void
   1558   af_cjk_align_serif_edge( AF_GlyphHints  hints,
   1559                            AF_Edge        base,
   1560                            AF_Edge        serif )
   1561   {
   1562     FT_UNUSED( hints );
   1563 
   1564     serif->pos = base->pos + ( serif->opos - base->opos );
   1565   }
   1566 
   1567 
   1568   /*************************************************************************/
   1569   /*************************************************************************/
   1570   /*************************************************************************/
   1571   /****                                                                 ****/
   1572   /****                    E D G E   H I N T I N G                      ****/
   1573   /****                                                                 ****/
   1574   /*************************************************************************/
   1575   /*************************************************************************/
   1576   /*************************************************************************/
   1577 
   1578 
   1579 #define AF_LIGHT_MODE_MAX_HORZ_GAP    9
   1580 #define AF_LIGHT_MODE_MAX_VERT_GAP   15
   1581 #define AF_LIGHT_MODE_MAX_DELTA_ABS  14
   1582 
   1583 
   1584   static FT_Pos
   1585   af_hint_normal_stem( AF_GlyphHints  hints,
   1586                        AF_Edge        edge,
   1587                        AF_Edge        edge2,
   1588                        FT_Pos         anchor,
   1589                        AF_Dimension   dim )
   1590   {
   1591     FT_Pos  org_len, cur_len, org_center;
   1592     FT_Pos  cur_pos1, cur_pos2;
   1593     FT_Pos  d_off1, u_off1, d_off2, u_off2, delta;
   1594     FT_Pos  offset;
   1595     FT_Pos  threshold = 64;
   1596 
   1597 
   1598     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
   1599     {
   1600       if ( ( edge->flags  & AF_EDGE_ROUND ) &&
   1601            ( edge2->flags & AF_EDGE_ROUND ) )
   1602       {
   1603         if ( dim == AF_DIMENSION_VERT )
   1604           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP;
   1605         else
   1606           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP;
   1607       }
   1608       else
   1609       {
   1610         if ( dim == AF_DIMENSION_VERT )
   1611           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP / 3;
   1612         else
   1613           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP / 3;
   1614       }
   1615     }
   1616 
   1617     org_len    = edge2->opos - edge->opos;
   1618     cur_len    = af_cjk_compute_stem_width( hints, dim, org_len,
   1619                                             edge->flags,
   1620                                             edge2->flags );
   1621 
   1622     org_center = ( edge->opos + edge2->opos ) / 2 + anchor;
   1623     cur_pos1   = org_center - cur_len / 2;
   1624     cur_pos2   = cur_pos1 + cur_len;
   1625     d_off1     = cur_pos1 - FT_PIX_FLOOR( cur_pos1 );
   1626     d_off2     = cur_pos2 - FT_PIX_FLOOR( cur_pos2 );
   1627     u_off1     = 64 - d_off1;
   1628     u_off2     = 64 - d_off2;
   1629     delta      = 0;
   1630 
   1631 
   1632     if ( d_off1 == 0 || d_off2 == 0 )
   1633       goto Exit;
   1634 
   1635     if ( cur_len <= threshold )
   1636     {
   1637       if ( d_off2 < cur_len )
   1638       {
   1639         if ( u_off1 <= d_off2 )
   1640           delta =  u_off1;
   1641         else
   1642           delta = -d_off2;
   1643       }
   1644 
   1645       goto Exit;
   1646     }
   1647 
   1648     if ( threshold < 64 )
   1649     {
   1650       if ( d_off1 >= threshold || u_off1 >= threshold ||
   1651            d_off2 >= threshold || u_off2 >= threshold )
   1652         goto Exit;
   1653     }
   1654 
   1655     offset = cur_len & 63;
   1656 
   1657     if ( offset < 32 )
   1658     {
   1659       if ( u_off1 <= offset || d_off2 <= offset )
   1660         goto Exit;
   1661     }
   1662     else
   1663       offset = 64 - threshold;
   1664 
   1665     d_off1 = threshold - u_off1;
   1666     u_off1 = u_off1    - offset;
   1667     u_off2 = threshold - d_off2;
   1668     d_off2 = d_off2    - offset;
   1669 
   1670     if ( d_off1 <= u_off1 )
   1671       u_off1 = -d_off1;
   1672 
   1673     if ( d_off2 <= u_off2 )
   1674       u_off2 = -d_off2;
   1675 
   1676     if ( FT_ABS( u_off1 ) <= FT_ABS( u_off2 ) )
   1677       delta = u_off1;
   1678     else
   1679       delta = u_off2;
   1680 
   1681   Exit:
   1682 
   1683 #if 1
   1684     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
   1685     {
   1686       if ( delta > AF_LIGHT_MODE_MAX_DELTA_ABS )
   1687         delta = AF_LIGHT_MODE_MAX_DELTA_ABS;
   1688       else if ( delta < -AF_LIGHT_MODE_MAX_DELTA_ABS )
   1689         delta = -AF_LIGHT_MODE_MAX_DELTA_ABS;
   1690     }
   1691 #endif
   1692 
   1693     cur_pos1 += delta;
   1694 
   1695     if ( edge->opos < edge2->opos )
   1696     {
   1697       edge->pos  = cur_pos1;
   1698       edge2->pos = cur_pos1 + cur_len;
   1699     }
   1700     else
   1701     {
   1702       edge->pos  = cur_pos1 + cur_len;
   1703       edge2->pos = cur_pos1;
   1704     }
   1705 
   1706     return delta;
   1707   }
   1708 
   1709 
   1710   /* The main grid-fitting routine. */
   1711 
   1712   static void
   1713   af_cjk_hint_edges( AF_GlyphHints  hints,
   1714                      AF_Dimension   dim )
   1715   {
   1716     AF_AxisHints  axis       = &hints->axis[dim];
   1717     AF_Edge       edges      = axis->edges;
   1718     AF_Edge       edge_limit = edges + axis->num_edges;
   1719     FT_PtrDist    n_edges;
   1720     AF_Edge       edge;
   1721     AF_Edge       anchor   = NULL;
   1722     FT_Pos        delta    = 0;
   1723     FT_Int        skipped  = 0;
   1724     FT_Bool       has_last_stem = FALSE;
   1725     FT_Pos        last_stem_pos = 0;
   1726 
   1727 #ifdef FT_DEBUG_LEVEL_TRACE
   1728     FT_UInt       num_actions = 0;
   1729 #endif
   1730 
   1731 
   1732     FT_TRACE5(( "cjk %s edge hinting (style `%s')\n",
   1733                 dim == AF_DIMENSION_VERT ? "horizontal" : "vertical",
   1734                 af_style_names[hints->metrics->style_class->style] ));
   1735 
   1736     /* we begin by aligning all stems relative to the blue zone */
   1737 
   1738     if ( AF_HINTS_DO_BLUES( hints ) )
   1739     {
   1740       for ( edge = edges; edge < edge_limit; edge++ )
   1741       {
   1742         AF_Width  blue;
   1743         AF_Edge   edge1, edge2;
   1744 
   1745 
   1746         if ( edge->flags & AF_EDGE_DONE )
   1747           continue;
   1748 
   1749         blue  = edge->blue_edge;
   1750         edge1 = NULL;
   1751         edge2 = edge->link;
   1752 
   1753         if ( blue )
   1754         {
   1755           edge1 = edge;
   1756         }
   1757         else if ( edge2 && edge2->blue_edge )
   1758         {
   1759           blue  = edge2->blue_edge;
   1760           edge1 = edge2;
   1761           edge2 = edge;
   1762         }
   1763 
   1764         if ( !edge1 )
   1765           continue;
   1766 
   1767 #ifdef FT_DEBUG_LEVEL_TRACE
   1768         FT_TRACE5(( "  CJKBLUE: edge %d @%d (opos=%.2f) snapped to %.2f,"
   1769                     " was %.2f\n",
   1770                     edge1 - edges, edge1->fpos, edge1->opos / 64.0,
   1771                     blue->fit / 64.0, edge1->pos / 64.0 ));
   1772 
   1773         num_actions++;
   1774 #endif
   1775 
   1776         edge1->pos    = blue->fit;
   1777         edge1->flags |= AF_EDGE_DONE;
   1778 
   1779         if ( edge2 && !edge2->blue_edge )
   1780         {
   1781           af_cjk_align_linked_edge( hints, dim, edge1, edge2 );
   1782           edge2->flags |= AF_EDGE_DONE;
   1783 
   1784 #ifdef FT_DEBUG_LEVEL_TRACE
   1785           num_actions++;
   1786 #endif
   1787         }
   1788 
   1789         if ( !anchor )
   1790           anchor = edge;
   1791       }
   1792     }
   1793 
   1794     /* now we align all stem edges. */
   1795     for ( edge = edges; edge < edge_limit; edge++ )
   1796     {
   1797       AF_Edge  edge2;
   1798 
   1799 
   1800       if ( edge->flags & AF_EDGE_DONE )
   1801         continue;
   1802 
   1803       /* skip all non-stem edges */
   1804       edge2 = edge->link;
   1805       if ( !edge2 )
   1806       {
   1807         skipped++;
   1808         continue;
   1809       }
   1810 
   1811       /* Some CJK characters have so many stems that
   1812        * the hinter is likely to merge two adjacent ones.
   1813        * To solve this problem, if either edge of a stem
   1814        * is too close to the previous one, we avoid
   1815        * aligning the two edges, but rather interpolate
   1816        * their locations at the end of this function in
   1817        * order to preserve the space between the stems.
   1818        */
   1819       if ( has_last_stem                       &&
   1820            ( edge->pos  < last_stem_pos + 64 ||
   1821              edge2->pos < last_stem_pos + 64 ) )
   1822       {
   1823         skipped++;
   1824         continue;
   1825       }
   1826 
   1827       /* now align the stem */
   1828 
   1829       /* this should not happen, but it's better to be safe */
   1830       if ( edge2->blue_edge )
   1831       {
   1832         FT_TRACE5(( "ASSERTION FAILED for edge %d\n", edge2-edges ));
   1833 
   1834         af_cjk_align_linked_edge( hints, dim, edge2, edge );
   1835         edge->flags |= AF_EDGE_DONE;
   1836 
   1837 #ifdef FT_DEBUG_LEVEL_TRACE
   1838         num_actions++;
   1839 #endif
   1840 
   1841         continue;
   1842       }
   1843 
   1844       if ( edge2 < edge )
   1845       {
   1846         af_cjk_align_linked_edge( hints, dim, edge2, edge );
   1847         edge->flags |= AF_EDGE_DONE;
   1848 
   1849 #ifdef FT_DEBUG_LEVEL_TRACE
   1850         num_actions++;
   1851 #endif
   1852 
   1853         /* We rarely reaches here it seems;
   1854          * usually the two edges belonging
   1855          * to one stem are marked as DONE together
   1856          */
   1857         has_last_stem = TRUE;
   1858         last_stem_pos = edge->pos;
   1859         continue;
   1860       }
   1861 
   1862       if ( dim != AF_DIMENSION_VERT && !anchor )
   1863       {
   1864 
   1865 #if 0
   1866         if ( fixedpitch )
   1867         {
   1868           AF_Edge     left  = edge;
   1869           AF_Edge     right = edge_limit - 1;
   1870           AF_EdgeRec  left1, left2, right1, right2;
   1871           FT_Pos      target, center1, center2;
   1872           FT_Pos      delta1, delta2, d1, d2;
   1873 
   1874 
   1875           while ( right > left && !right->link )
   1876             right--;
   1877 
   1878           left1  = *left;
   1879           left2  = *left->link;
   1880           right1 = *right->link;
   1881           right2 = *right;
   1882 
   1883           delta  = ( ( ( hinter->pp2.x + 32 ) & -64 ) - hinter->pp2.x ) / 2;
   1884           target = left->opos + ( right->opos - left->opos ) / 2 + delta - 16;
   1885 
   1886           delta1  = delta;
   1887           delta1 += af_hint_normal_stem( hints, left, left->link,
   1888                                          delta1, 0 );
   1889 
   1890           if ( left->link != right )
   1891             af_hint_normal_stem( hints, right->link, right, delta1, 0 );
   1892 
   1893           center1 = left->pos + ( right->pos - left->pos ) / 2;
   1894 
   1895           if ( center1 >= target )
   1896             delta2 = delta - 32;
   1897           else
   1898             delta2 = delta + 32;
   1899 
   1900           delta2 += af_hint_normal_stem( hints, &left1, &left2, delta2, 0 );
   1901 
   1902           if ( delta1 != delta2 )
   1903           {
   1904             if ( left->link != right )
   1905               af_hint_normal_stem( hints, &right1, &right2, delta2, 0 );
   1906 
   1907             center2 = left1.pos + ( right2.pos - left1.pos ) / 2;
   1908 
   1909             d1 = center1 - target;
   1910             d2 = center2 - target;
   1911 
   1912             if ( FT_ABS( d2 ) < FT_ABS( d1 ) )
   1913             {
   1914               left->pos       = left1.pos;
   1915               left->link->pos = left2.pos;
   1916 
   1917               if ( left->link != right )
   1918               {
   1919                 right->link->pos = right1.pos;
   1920                 right->pos       = right2.pos;
   1921               }
   1922 
   1923               delta1 = delta2;
   1924             }
   1925           }
   1926 
   1927           delta               = delta1;
   1928           right->link->flags |= AF_EDGE_DONE;
   1929           right->flags       |= AF_EDGE_DONE;
   1930         }
   1931         else
   1932 
   1933 #endif /* 0 */
   1934 
   1935           delta = af_hint_normal_stem( hints, edge, edge2, 0,
   1936                                        AF_DIMENSION_HORZ );
   1937       }
   1938       else
   1939         af_hint_normal_stem( hints, edge, edge2, delta, dim );
   1940 
   1941 #if 0
   1942       printf( "stem (%d,%d) adjusted (%.1f,%.1f)\n",
   1943                edge - edges, edge2 - edges,
   1944                ( edge->pos - edge->opos ) / 64.0,
   1945                ( edge2->pos - edge2->opos ) / 64.0 );
   1946 #endif
   1947 
   1948       anchor = edge;
   1949       edge->flags  |= AF_EDGE_DONE;
   1950       edge2->flags |= AF_EDGE_DONE;
   1951       has_last_stem = TRUE;
   1952       last_stem_pos = edge2->pos;
   1953     }
   1954 
   1955     /* make sure that lowercase m's maintain their symmetry */
   1956 
   1957     /* In general, lowercase m's have six vertical edges if they are sans */
   1958     /* serif, or twelve if they are with serifs.  This implementation is  */
   1959     /* based on that assumption, and seems to work very well with most    */
   1960     /* faces.  However, if for a certain face this assumption is not      */
   1961     /* true, the m is just rendered like before.  In addition, any stem   */
   1962     /* correction will only be applied to symmetrical glyphs (even if the */
   1963     /* glyph is not an m), so the potential for unwanted distortion is    */
   1964     /* relatively low.                                                    */
   1965 
   1966     /* We don't handle horizontal edges since we can't easily assure that */
   1967     /* the third (lowest) stem aligns with the base line; it might end up */
   1968     /* one pixel higher or lower.                                         */
   1969 
   1970     n_edges = edge_limit - edges;
   1971     if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
   1972     {
   1973       AF_Edge  edge1, edge2, edge3;
   1974       FT_Pos   dist1, dist2, span;
   1975 
   1976 
   1977       if ( n_edges == 6 )
   1978       {
   1979         edge1 = edges;
   1980         edge2 = edges + 2;
   1981         edge3 = edges + 4;
   1982       }
   1983       else
   1984       {
   1985         edge1 = edges + 1;
   1986         edge2 = edges + 5;
   1987         edge3 = edges + 9;
   1988       }
   1989 
   1990       dist1 = edge2->opos - edge1->opos;
   1991       dist2 = edge3->opos - edge2->opos;
   1992 
   1993       span = dist1 - dist2;
   1994       if ( span < 0 )
   1995         span = -span;
   1996 
   1997       if ( edge1->link == edge1 + 1 &&
   1998            edge2->link == edge2 + 1 &&
   1999            edge3->link == edge3 + 1 && span < 8 )
   2000       {
   2001         delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
   2002         edge3->pos -= delta;
   2003         if ( edge3->link )
   2004           edge3->link->pos -= delta;
   2005 
   2006         /* move the serifs along with the stem */
   2007         if ( n_edges == 12 )
   2008         {
   2009           ( edges + 8 )->pos -= delta;
   2010           ( edges + 11 )->pos -= delta;
   2011         }
   2012 
   2013         edge3->flags |= AF_EDGE_DONE;
   2014         if ( edge3->link )
   2015           edge3->link->flags |= AF_EDGE_DONE;
   2016       }
   2017     }
   2018 
   2019     if ( !skipped )
   2020       goto Exit;
   2021 
   2022     /*
   2023      *  now hint the remaining edges (serifs and single) in order
   2024      *  to complete our processing
   2025      */
   2026     for ( edge = edges; edge < edge_limit; edge++ )
   2027     {
   2028       if ( edge->flags & AF_EDGE_DONE )
   2029         continue;
   2030 
   2031       if ( edge->serif )
   2032       {
   2033         af_cjk_align_serif_edge( hints, edge->serif, edge );
   2034         edge->flags |= AF_EDGE_DONE;
   2035         skipped--;
   2036       }
   2037     }
   2038 
   2039     if ( !skipped )
   2040       goto Exit;
   2041 
   2042     for ( edge = edges; edge < edge_limit; edge++ )
   2043     {
   2044       AF_Edge  before, after;
   2045 
   2046 
   2047       if ( edge->flags & AF_EDGE_DONE )
   2048         continue;
   2049 
   2050       before = after = edge;
   2051 
   2052       while ( --before >= edges )
   2053         if ( before->flags & AF_EDGE_DONE )
   2054           break;
   2055 
   2056       while ( ++after < edge_limit )
   2057         if ( after->flags & AF_EDGE_DONE )
   2058           break;
   2059 
   2060       if ( before >= edges || after < edge_limit )
   2061       {
   2062         if ( before < edges )
   2063           af_cjk_align_serif_edge( hints, after, edge );
   2064         else if ( after >= edge_limit )
   2065           af_cjk_align_serif_edge( hints, before, edge );
   2066         else
   2067         {
   2068           if ( after->fpos == before->fpos )
   2069             edge->pos = before->pos;
   2070           else
   2071             edge->pos = before->pos +
   2072                         FT_MulDiv( edge->fpos - before->fpos,
   2073                                    after->pos - before->pos,
   2074                                    after->fpos - before->fpos );
   2075         }
   2076       }
   2077     }
   2078 
   2079   Exit:
   2080 
   2081 #ifdef FT_DEBUG_LEVEL_TRACE
   2082     if ( !num_actions )
   2083       FT_TRACE5(( "  (none)\n" ));
   2084     FT_TRACE5(( "\n" ));
   2085 #endif
   2086 
   2087     return;
   2088   }
   2089 
   2090 
   2091   static void
   2092   af_cjk_align_edge_points( AF_GlyphHints  hints,
   2093                             AF_Dimension   dim )
   2094   {
   2095     AF_AxisHints  axis       = & hints->axis[dim];
   2096     AF_Edge       edges      = axis->edges;
   2097     AF_Edge       edge_limit = edges + axis->num_edges;
   2098     AF_Edge       edge;
   2099     FT_Bool       snapping;
   2100 
   2101 
   2102     snapping = FT_BOOL( ( dim == AF_DIMENSION_HORZ             &&
   2103                           AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) )  ||
   2104                         ( dim == AF_DIMENSION_VERT             &&
   2105                           AF_LATIN_HINTS_DO_VERT_SNAP( hints ) )  );
   2106 
   2107     for ( edge = edges; edge < edge_limit; edge++ )
   2108     {
   2109       /* move the points of each segment     */
   2110       /* in each edge to the edge's position */
   2111       AF_Segment  seg = edge->first;
   2112 
   2113 
   2114       if ( snapping )
   2115       {
   2116         do
   2117         {
   2118           AF_Point  point = seg->first;
   2119 
   2120 
   2121           for (;;)
   2122           {
   2123             if ( dim == AF_DIMENSION_HORZ )
   2124             {
   2125               point->x      = edge->pos;
   2126               point->flags |= AF_FLAG_TOUCH_X;
   2127             }
   2128             else
   2129             {
   2130               point->y      = edge->pos;
   2131               point->flags |= AF_FLAG_TOUCH_Y;
   2132             }
   2133 
   2134             if ( point == seg->last )
   2135               break;
   2136 
   2137             point = point->next;
   2138           }
   2139 
   2140           seg = seg->edge_next;
   2141 
   2142         } while ( seg != edge->first );
   2143       }
   2144       else
   2145       {
   2146         FT_Pos  delta = edge->pos - edge->opos;
   2147 
   2148 
   2149         do
   2150         {
   2151           AF_Point  point = seg->first;
   2152 
   2153 
   2154           for (;;)
   2155           {
   2156             if ( dim == AF_DIMENSION_HORZ )
   2157             {
   2158               point->x     += delta;
   2159               point->flags |= AF_FLAG_TOUCH_X;
   2160             }
   2161             else
   2162             {
   2163               point->y     += delta;
   2164               point->flags |= AF_FLAG_TOUCH_Y;
   2165             }
   2166 
   2167             if ( point == seg->last )
   2168               break;
   2169 
   2170             point = point->next;
   2171           }
   2172 
   2173           seg = seg->edge_next;
   2174 
   2175         } while ( seg != edge->first );
   2176       }
   2177     }
   2178   }
   2179 
   2180 
   2181   /* Apply the complete hinting algorithm to a CJK glyph. */
   2182 
   2183   FT_LOCAL_DEF( FT_Error )
   2184   af_cjk_hints_apply( AF_GlyphHints  hints,
   2185                       FT_Outline*    outline,
   2186                       AF_CJKMetrics  metrics )
   2187   {
   2188     FT_Error  error;
   2189     int       dim;
   2190 
   2191     FT_UNUSED( metrics );
   2192 
   2193 
   2194     error = af_glyph_hints_reload( hints, outline );
   2195     if ( error )
   2196       goto Exit;
   2197 
   2198     /* analyze glyph outline */
   2199 #ifdef AF_CONFIG_OPTION_USE_WARPER
   2200     if ( ( metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT &&
   2201            AF_HINTS_DO_WARP( hints )                                ) ||
   2202          AF_HINTS_DO_HORIZONTAL( hints )                              )
   2203 #else
   2204     if ( AF_HINTS_DO_HORIZONTAL( hints ) )
   2205 #endif
   2206     {
   2207       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_HORZ );
   2208       if ( error )
   2209         goto Exit;
   2210 
   2211       af_cjk_hints_compute_blue_edges( hints, metrics, AF_DIMENSION_HORZ );
   2212     }
   2213 
   2214     if ( AF_HINTS_DO_VERTICAL( hints ) )
   2215     {
   2216       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_VERT );
   2217       if ( error )
   2218         goto Exit;
   2219 
   2220       af_cjk_hints_compute_blue_edges( hints, metrics, AF_DIMENSION_VERT );
   2221     }
   2222 
   2223     /* grid-fit the outline */
   2224     for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
   2225     {
   2226       if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) ||
   2227            ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) )   )
   2228       {
   2229 
   2230 #ifdef AF_CONFIG_OPTION_USE_WARPER
   2231         if ( dim == AF_DIMENSION_HORZ                                 &&
   2232              metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT &&
   2233              AF_HINTS_DO_WARP( hints )                                )
   2234         {
   2235           AF_WarperRec  warper;
   2236           FT_Fixed      scale;
   2237           FT_Pos        delta;
   2238 
   2239 
   2240           af_warper_compute( &warper, hints, (AF_Dimension)dim,
   2241                              &scale, &delta );
   2242           af_glyph_hints_scale_dim( hints, (AF_Dimension)dim,
   2243                                     scale, delta );
   2244           continue;
   2245         }
   2246 #endif /* AF_CONFIG_OPTION_USE_WARPER */
   2247 
   2248         af_cjk_hint_edges( hints, (AF_Dimension)dim );
   2249         af_cjk_align_edge_points( hints, (AF_Dimension)dim );
   2250         af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
   2251         af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
   2252       }
   2253     }
   2254 
   2255     af_glyph_hints_save( hints, outline );
   2256 
   2257   Exit:
   2258     return error;
   2259   }
   2260 
   2261 
   2262   /*************************************************************************/
   2263   /*************************************************************************/
   2264   /*****                                                               *****/
   2265   /*****                C J K   S C R I P T   C L A S S                *****/
   2266   /*****                                                               *****/
   2267   /*************************************************************************/
   2268   /*************************************************************************/
   2269 
   2270 
   2271   AF_DEFINE_WRITING_SYSTEM_CLASS(
   2272     af_cjk_writing_system_class,
   2273 
   2274     AF_WRITING_SYSTEM_CJK,
   2275 
   2276     sizeof ( AF_CJKMetricsRec ),
   2277 
   2278     (AF_WritingSystem_InitMetricsFunc) af_cjk_metrics_init,
   2279     (AF_WritingSystem_ScaleMetricsFunc)af_cjk_metrics_scale,
   2280     (AF_WritingSystem_DoneMetricsFunc) NULL,
   2281 
   2282     (AF_WritingSystem_InitHintsFunc)   af_cjk_hints_init,
   2283     (AF_WritingSystem_ApplyHintsFunc)  af_cjk_hints_apply
   2284   )
   2285 
   2286 
   2287 #else /* !AF_CONFIG_OPTION_CJK */
   2288 
   2289 
   2290   AF_DEFINE_WRITING_SYSTEM_CLASS(
   2291     af_cjk_writing_system_class,
   2292 
   2293     AF_WRITING_SYSTEM_CJK,
   2294 
   2295     sizeof ( AF_CJKMetricsRec ),
   2296 
   2297     (AF_WritingSystem_InitMetricsFunc) NULL,
   2298     (AF_WritingSystem_ScaleMetricsFunc)NULL,
   2299     (AF_WritingSystem_DoneMetricsFunc) NULL,
   2300 
   2301     (AF_WritingSystem_InitHintsFunc)   NULL,
   2302     (AF_WritingSystem_ApplyHintsFunc)  NULL
   2303   )
   2304 
   2305 
   2306 #endif /* !AF_CONFIG_OPTION_CJK */
   2307 
   2308 
   2309 /* END */
   2310