Home | History | Annotate | Download | only in autofit
      1 /***************************************************************************/
      2 /*                                                                         */
      3 /*  afcjk.c                                                                */
      4 /*                                                                         */
      5 /*    Auto-fitter hinting routines for CJK script (body).                  */
      6 /*                                                                         */
      7 /*  Copyright 2006, 2007, 2008, 2009, 2010 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 "aftypes.h"
     26 #include "aflatin.h"
     27 
     28 
     29 #ifdef AF_CONFIG_OPTION_CJK
     30 
     31 #include "afcjk.h"
     32 #include "aferrors.h"
     33 
     34 
     35 #ifdef AF_USE_WARPER
     36 #include "afwarp.h"
     37 #endif
     38 
     39 
     40   /*************************************************************************/
     41   /*************************************************************************/
     42   /*****                                                               *****/
     43   /*****              C J K   G L O B A L   M E T R I C S              *****/
     44   /*****                                                               *****/
     45   /*************************************************************************/
     46   /*************************************************************************/
     47 
     48   FT_LOCAL_DEF( FT_Error )
     49   af_cjk_metrics_init( AF_LatinMetrics  metrics,
     50                        FT_Face          face )
     51   {
     52     FT_CharMap  oldmap = face->charmap;
     53 
     54 
     55     metrics->units_per_em = face->units_per_EM;
     56 
     57     /* TODO are there blues? */
     58 
     59     if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
     60       face->charmap = NULL;
     61     else
     62     {
     63       /* latin's version would suffice */
     64       af_latin_metrics_init_widths( metrics, face, 0x7530 );
     65       af_latin_metrics_check_digits( metrics, face );
     66     }
     67 
     68     FT_Set_Charmap( face, oldmap );
     69 
     70     return AF_Err_Ok;
     71   }
     72 
     73 
     74   static void
     75   af_cjk_metrics_scale_dim( AF_LatinMetrics  metrics,
     76                             AF_Scaler        scaler,
     77                             AF_Dimension     dim )
     78   {
     79     AF_LatinAxis  axis;
     80 
     81 
     82     axis = &metrics->axis[dim];
     83 
     84     if ( dim == AF_DIMENSION_HORZ )
     85     {
     86       axis->scale = scaler->x_scale;
     87       axis->delta = scaler->x_delta;
     88     }
     89     else
     90     {
     91       axis->scale = scaler->y_scale;
     92       axis->delta = scaler->y_delta;
     93     }
     94   }
     95 
     96 
     97   FT_LOCAL_DEF( void )
     98   af_cjk_metrics_scale( AF_LatinMetrics  metrics,
     99                         AF_Scaler        scaler )
    100   {
    101     metrics->root.scaler = *scaler;
    102 
    103     af_cjk_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
    104     af_cjk_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
    105   }
    106 
    107 
    108   /*************************************************************************/
    109   /*************************************************************************/
    110   /*****                                                               *****/
    111   /*****              C J K   G L Y P H   A N A L Y S I S              *****/
    112   /*****                                                               *****/
    113   /*************************************************************************/
    114   /*************************************************************************/
    115 
    116   static FT_Error
    117   af_cjk_hints_compute_segments( AF_GlyphHints  hints,
    118                                  AF_Dimension   dim )
    119   {
    120     AF_AxisHints  axis          = &hints->axis[dim];
    121     AF_Segment    segments      = axis->segments;
    122     AF_Segment    segment_limit = segments + axis->num_segments;
    123     FT_Error      error;
    124     AF_Segment    seg;
    125 
    126 
    127     error = af_latin_hints_compute_segments( hints, dim );
    128     if ( error )
    129       return error;
    130 
    131     /* a segment is round if it doesn't have successive */
    132     /* on-curve points.                                 */
    133     for ( seg = segments; seg < segment_limit; seg++ )
    134     {
    135       AF_Point  pt   = seg->first;
    136       AF_Point  last = seg->last;
    137       AF_Flags  f0   = (AF_Flags)(pt->flags & AF_FLAG_CONTROL);
    138       AF_Flags  f1;
    139 
    140 
    141       seg->flags &= ~AF_EDGE_ROUND;
    142 
    143       for ( ; pt != last; f0 = f1 )
    144       {
    145         pt = pt->next;
    146         f1 = (AF_Flags)(pt->flags & AF_FLAG_CONTROL);
    147 
    148         if ( !f0 && !f1 )
    149           break;
    150 
    151         if ( pt == last )
    152           seg->flags |= AF_EDGE_ROUND;
    153       }
    154     }
    155 
    156     return AF_Err_Ok;
    157   }
    158 
    159 
    160   static void
    161   af_cjk_hints_link_segments( AF_GlyphHints  hints,
    162                               AF_Dimension   dim )
    163   {
    164     AF_AxisHints  axis          = &hints->axis[dim];
    165     AF_Segment    segments      = axis->segments;
    166     AF_Segment    segment_limit = segments + axis->num_segments;
    167     AF_Direction  major_dir     = axis->major_dir;
    168     AF_Segment    seg1, seg2;
    169     FT_Pos        len_threshold;
    170     FT_Pos        dist_threshold;
    171 
    172 
    173     len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 );
    174 
    175     dist_threshold = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
    176                                                   : hints->y_scale;
    177     dist_threshold = FT_DivFix( 64 * 3, dist_threshold );
    178 
    179     /* now compare each segment to the others */
    180     for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    181     {
    182       /* the fake segments are for metrics hinting only */
    183       if ( seg1->first == seg1->last )
    184         continue;
    185 
    186       if ( seg1->dir != major_dir )
    187         continue;
    188 
    189       for ( seg2 = segments; seg2 < segment_limit; seg2++ )
    190         if ( seg2 != seg1 && seg1->dir + seg2->dir == 0 )
    191         {
    192           FT_Pos  dist = seg2->pos - seg1->pos;
    193 
    194 
    195           if ( dist < 0 )
    196             continue;
    197 
    198           {
    199             FT_Pos  min = seg1->min_coord;
    200             FT_Pos  max = seg1->max_coord;
    201             FT_Pos  len;
    202 
    203 
    204             if ( min < seg2->min_coord )
    205               min = seg2->min_coord;
    206 
    207             if ( max > seg2->max_coord )
    208               max = seg2->max_coord;
    209 
    210             len = max - min;
    211             if ( len >= len_threshold )
    212             {
    213               if ( dist * 8 < seg1->score * 9                        &&
    214                    ( dist * 8 < seg1->score * 7 || seg1->len < len ) )
    215               {
    216                 seg1->score = dist;
    217                 seg1->len   = len;
    218                 seg1->link  = seg2;
    219               }
    220 
    221               if ( dist * 8 < seg2->score * 9                        &&
    222                    ( dist * 8 < seg2->score * 7 || seg2->len < len ) )
    223               {
    224                 seg2->score = dist;
    225                 seg2->len   = len;
    226                 seg2->link  = seg1;
    227               }
    228             }
    229           }
    230         }
    231     }
    232 
    233     /*
    234      *  now compute the `serif' segments
    235      *
    236      *  In Hanzi, some strokes are wider on one or both of the ends.
    237      *  We either identify the stems on the ends as serifs or remove
    238      *  the linkage, depending on the length of the stems.
    239      *
    240      */
    241 
    242     {
    243       AF_Segment  link1, link2;
    244 
    245 
    246       for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    247       {
    248         link1 = seg1->link;
    249         if ( !link1 || link1->link != seg1 || link1->pos <= seg1->pos )
    250           continue;
    251 
    252         if ( seg1->score >= dist_threshold )
    253           continue;
    254 
    255         for ( seg2 = segments; seg2 < segment_limit; seg2++ )
    256         {
    257           if ( seg2->pos > seg1->pos || seg1 == seg2 )
    258             continue;
    259 
    260           link2 = seg2->link;
    261           if ( !link2 || link2->link != seg2 || link2->pos < link1->pos )
    262             continue;
    263 
    264           if ( seg1->pos == seg2->pos && link1->pos == link2->pos )
    265             continue;
    266 
    267           if ( seg2->score <= seg1->score || seg1->score * 4 <= seg2->score )
    268             continue;
    269 
    270           /* seg2 < seg1 < link1 < link2 */
    271 
    272           if ( seg1->len >= seg2->len * 3 )
    273           {
    274             AF_Segment  seg;
    275 
    276 
    277             for ( seg = segments; seg < segment_limit; seg++ )
    278             {
    279               AF_Segment  link = seg->link;
    280 
    281 
    282               if ( link == seg2 )
    283               {
    284                 seg->link  = 0;
    285                 seg->serif = link1;
    286               }
    287               else if ( link == link2 )
    288               {
    289                 seg->link  = 0;
    290                 seg->serif = seg1;
    291               }
    292             }
    293           }
    294           else
    295           {
    296             seg1->link = link1->link = 0;
    297 
    298             break;
    299           }
    300         }
    301       }
    302     }
    303 
    304     for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    305     {
    306       seg2 = seg1->link;
    307 
    308       if ( seg2 )
    309       {
    310         seg2->num_linked++;
    311         if ( seg2->link != seg1 )
    312         {
    313           seg1->link = 0;
    314 
    315           if ( seg2->score < dist_threshold || seg1->score < seg2->score * 4 )
    316             seg1->serif = seg2->link;
    317           else
    318             seg2->num_linked--;
    319         }
    320       }
    321     }
    322   }
    323 
    324 
    325   static FT_Error
    326   af_cjk_hints_compute_edges( AF_GlyphHints  hints,
    327                               AF_Dimension   dim )
    328   {
    329     AF_AxisHints  axis   = &hints->axis[dim];
    330     FT_Error      error  = AF_Err_Ok;
    331     FT_Memory     memory = hints->memory;
    332     AF_LatinAxis  laxis  = &((AF_LatinMetrics)hints->metrics)->axis[dim];
    333 
    334     AF_Segment    segments      = axis->segments;
    335     AF_Segment    segment_limit = segments + axis->num_segments;
    336     AF_Segment    seg;
    337 
    338     FT_Fixed      scale;
    339     FT_Pos        edge_distance_threshold;
    340 
    341 
    342     axis->num_edges = 0;
    343 
    344     scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
    345                                          : hints->y_scale;
    346 
    347     /*********************************************************************/
    348     /*                                                                   */
    349     /* We begin by generating a sorted table of edges for the current    */
    350     /* direction.  To do so, we simply scan each segment and try to find */
    351     /* an edge in our table that corresponds to its position.            */
    352     /*                                                                   */
    353     /* If no edge is found, we create and insert a new edge in the       */
    354     /* sorted table.  Otherwise, we simply add the segment to the edge's */
    355     /* list which is then processed in the second step to compute the    */
    356     /* edge's properties.                                                */
    357     /*                                                                   */
    358     /* Note that the edges table is sorted along the segment/edge        */
    359     /* position.                                                         */
    360     /*                                                                   */
    361     /*********************************************************************/
    362 
    363     edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
    364                                          scale );
    365     if ( edge_distance_threshold > 64 / 4 )
    366       edge_distance_threshold = FT_DivFix( 64 / 4, scale );
    367     else
    368       edge_distance_threshold = laxis->edge_distance_threshold;
    369 
    370     for ( seg = segments; seg < segment_limit; seg++ )
    371     {
    372       AF_Edge  found = 0;
    373       FT_Pos   best  = 0xFFFFU;
    374       FT_Int   ee;
    375 
    376 
    377       /* look for an edge corresponding to the segment */
    378       for ( ee = 0; ee < axis->num_edges; ee++ )
    379       {
    380         AF_Edge  edge = axis->edges + ee;
    381         FT_Pos   dist;
    382 
    383 
    384         if ( edge->dir != seg->dir )
    385           continue;
    386 
    387         dist = seg->pos - edge->fpos;
    388         if ( dist < 0 )
    389           dist = -dist;
    390 
    391         if ( dist < edge_distance_threshold && dist < best )
    392         {
    393           AF_Segment  link = seg->link;
    394 
    395 
    396           /* check whether all linked segments of the candidate edge */
    397           /* can make a single edge.                                 */
    398           if ( link )
    399           {
    400             AF_Segment  seg1 = edge->first;
    401             AF_Segment  link1;
    402             FT_Pos      dist2 = 0;
    403 
    404 
    405             do
    406             {
    407               link1 = seg1->link;
    408               if ( link1 )
    409               {
    410                 dist2 = AF_SEGMENT_DIST( link, link1 );
    411                 if ( dist2 >= edge_distance_threshold )
    412                   break;
    413               }
    414 
    415             } while ( ( seg1 = seg1->edge_next ) != edge->first );
    416 
    417             if ( dist2 >= edge_distance_threshold )
    418               continue;
    419           }
    420 
    421           best  = dist;
    422           found = edge;
    423         }
    424       }
    425 
    426       if ( !found )
    427       {
    428         AF_Edge  edge;
    429 
    430 
    431         /* insert a new edge in the list and */
    432         /* sort according to the position    */
    433         error = af_axis_hints_new_edge( axis, seg->pos,
    434                                         (AF_Direction)seg->dir,
    435                                         memory, &edge );
    436         if ( error )
    437           goto Exit;
    438 
    439         /* add the segment to the new edge's list */
    440         FT_ZERO( edge );
    441 
    442         edge->first    = seg;
    443         edge->last     = seg;
    444         edge->fpos     = seg->pos;
    445         edge->opos     = edge->pos = FT_MulFix( seg->pos, scale );
    446         seg->edge_next = seg;
    447         edge->dir      = seg->dir;
    448       }
    449       else
    450       {
    451         /* if an edge was found, simply add the segment to the edge's */
    452         /* list                                                       */
    453         seg->edge_next         = found->first;
    454         found->last->edge_next = seg;
    455         found->last            = seg;
    456       }
    457     }
    458 
    459     /*********************************************************************/
    460     /*                                                                   */
    461     /* Good, we now compute each edge's properties according to segments */
    462     /* found on its position.  Basically, these are as follows.          */
    463     /*                                                                   */
    464     /*  - edge's main direction                                          */
    465     /*  - stem edge, serif edge or both (which defaults to stem then)    */
    466     /*  - rounded edge, straight or both (which defaults to straight)    */
    467     /*  - link for edge                                                  */
    468     /*                                                                   */
    469     /*********************************************************************/
    470 
    471     /* first of all, set the `edge' field in each segment -- this is     */
    472     /* required in order to compute edge links                           */
    473     /*                                                                   */
    474     /* Note that removing this loop and setting the `edge' field of each */
    475     /* segment directly in the code above slows down execution speed for */
    476     /* some reasons on platforms like the Sun.                           */
    477 
    478     {
    479       AF_Edge  edges      = axis->edges;
    480       AF_Edge  edge_limit = edges + axis->num_edges;
    481       AF_Edge  edge;
    482 
    483 
    484       for ( edge = edges; edge < edge_limit; edge++ )
    485       {
    486         seg = edge->first;
    487         if ( seg )
    488           do
    489           {
    490             seg->edge = edge;
    491             seg       = seg->edge_next;
    492 
    493           } while ( seg != edge->first );
    494       }
    495 
    496       /* now compute each edge properties */
    497       for ( edge = edges; edge < edge_limit; edge++ )
    498       {
    499         FT_Int  is_round    = 0;  /* does it contain round segments?    */
    500         FT_Int  is_straight = 0;  /* does it contain straight segments? */
    501 
    502 
    503         seg = edge->first;
    504 
    505         do
    506         {
    507           FT_Bool  is_serif;
    508 
    509 
    510           /* check for roundness of segment */
    511           if ( seg->flags & AF_EDGE_ROUND )
    512             is_round++;
    513           else
    514             is_straight++;
    515 
    516           /* check for links -- if seg->serif is set, then seg->link must */
    517           /* be ignored                                                   */
    518           is_serif = (FT_Bool)( seg->serif && seg->serif->edge != edge );
    519 
    520           if ( seg->link || is_serif )
    521           {
    522             AF_Edge     edge2;
    523             AF_Segment  seg2;
    524 
    525 
    526             edge2 = edge->link;
    527             seg2  = seg->link;
    528 
    529             if ( is_serif )
    530             {
    531               seg2  = seg->serif;
    532               edge2 = edge->serif;
    533             }
    534 
    535             if ( edge2 )
    536             {
    537               FT_Pos  edge_delta;
    538               FT_Pos  seg_delta;
    539 
    540 
    541               edge_delta = edge->fpos - edge2->fpos;
    542               if ( edge_delta < 0 )
    543                 edge_delta = -edge_delta;
    544 
    545               seg_delta = AF_SEGMENT_DIST( seg, seg2 );
    546 
    547               if ( seg_delta < edge_delta )
    548                 edge2 = seg2->edge;
    549             }
    550             else
    551               edge2 = seg2->edge;
    552 
    553             if ( is_serif )
    554             {
    555               edge->serif   = edge2;
    556               edge2->flags |= AF_EDGE_SERIF;
    557             }
    558             else
    559               edge->link  = edge2;
    560           }
    561 
    562           seg = seg->edge_next;
    563 
    564         } while ( seg != edge->first );
    565 
    566         /* set the round/straight flags */
    567         edge->flags = AF_EDGE_NORMAL;
    568 
    569         if ( is_round > 0 && is_round >= is_straight )
    570           edge->flags |= AF_EDGE_ROUND;
    571 
    572         /* get rid of serifs if link is set                 */
    573         /* XXX: This gets rid of many unpleasant artefacts! */
    574         /*      Example: the `c' in cour.pfa at size 13     */
    575 
    576         if ( edge->serif && edge->link )
    577           edge->serif = 0;
    578       }
    579     }
    580 
    581   Exit:
    582     return error;
    583   }
    584 
    585 
    586   static FT_Error
    587   af_cjk_hints_detect_features( AF_GlyphHints  hints,
    588                                 AF_Dimension   dim )
    589   {
    590     FT_Error  error;
    591 
    592 
    593     error = af_cjk_hints_compute_segments( hints, dim );
    594     if ( !error )
    595     {
    596       af_cjk_hints_link_segments( hints, dim );
    597 
    598       error = af_cjk_hints_compute_edges( hints, dim );
    599     }
    600     return error;
    601   }
    602 
    603 
    604   FT_LOCAL_DEF( FT_Error )
    605   af_cjk_hints_init( AF_GlyphHints    hints,
    606                      AF_LatinMetrics  metrics )
    607   {
    608     FT_Render_Mode  mode;
    609     FT_UInt32       scaler_flags, other_flags;
    610 
    611 
    612     af_glyph_hints_rescale( hints, (AF_ScriptMetrics)metrics );
    613 
    614     /*
    615      *  correct x_scale and y_scale when needed, since they may have
    616      *  been modified af_cjk_scale_dim above
    617      */
    618     hints->x_scale = metrics->axis[AF_DIMENSION_HORZ].scale;
    619     hints->x_delta = metrics->axis[AF_DIMENSION_HORZ].delta;
    620     hints->y_scale = metrics->axis[AF_DIMENSION_VERT].scale;
    621     hints->y_delta = metrics->axis[AF_DIMENSION_VERT].delta;
    622 
    623     /* compute flags depending on render mode, etc. */
    624     mode = metrics->root.scaler.render_mode;
    625 
    626 #ifdef AF_USE_WARPER
    627     if ( mode == FT_RENDER_MODE_LCD || mode == FT_RENDER_MODE_LCD_V )
    628       metrics->root.scaler.render_mode = mode = FT_RENDER_MODE_NORMAL;
    629 #endif
    630 
    631     scaler_flags = hints->scaler_flags;
    632     other_flags  = 0;
    633 
    634     /*
    635      *  We snap the width of vertical stems for the monochrome and
    636      *  horizontal LCD rendering targets only.
    637      */
    638     if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD )
    639       other_flags |= AF_LATIN_HINTS_HORZ_SNAP;
    640 
    641     /*
    642      *  We snap the width of horizontal stems for the monochrome and
    643      *  vertical LCD rendering targets only.
    644      */
    645     if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V )
    646       other_flags |= AF_LATIN_HINTS_VERT_SNAP;
    647 
    648     /*
    649      *  We adjust stems to full pixels only if we don't use the `light' mode.
    650      */
    651     if ( mode != FT_RENDER_MODE_LIGHT )
    652       other_flags |= AF_LATIN_HINTS_STEM_ADJUST;
    653 
    654     if ( mode == FT_RENDER_MODE_MONO )
    655       other_flags |= AF_LATIN_HINTS_MONO;
    656 
    657     scaler_flags |= AF_SCALER_FLAG_NO_ADVANCE;
    658 
    659     hints->scaler_flags = scaler_flags;
    660     hints->other_flags  = other_flags;
    661 
    662     return 0;
    663   }
    664 
    665 
    666   /*************************************************************************/
    667   /*************************************************************************/
    668   /*****                                                               *****/
    669   /*****          C J K   G L Y P H   G R I D - F I T T I N G          *****/
    670   /*****                                                               *****/
    671   /*************************************************************************/
    672   /*************************************************************************/
    673 
    674   /* snap a given width in scaled coordinates to one of the */
    675   /* current standard widths                                */
    676 
    677   static FT_Pos
    678   af_cjk_snap_width( AF_Width  widths,
    679                      FT_Int    count,
    680                      FT_Pos    width )
    681   {
    682     int     n;
    683     FT_Pos  best      = 64 + 32 + 2;
    684     FT_Pos  reference = width;
    685     FT_Pos  scaled;
    686 
    687 
    688     for ( n = 0; n < count; n++ )
    689     {
    690       FT_Pos  w;
    691       FT_Pos  dist;
    692 
    693 
    694       w = widths[n].cur;
    695       dist = width - w;
    696       if ( dist < 0 )
    697         dist = -dist;
    698       if ( dist < best )
    699       {
    700         best      = dist;
    701         reference = w;
    702       }
    703     }
    704 
    705     scaled = FT_PIX_ROUND( reference );
    706 
    707     if ( width >= reference )
    708     {
    709       if ( width < scaled + 48 )
    710         width = reference;
    711     }
    712     else
    713     {
    714       if ( width > scaled - 48 )
    715         width = reference;
    716     }
    717 
    718     return width;
    719   }
    720 
    721 
    722   /* compute the snapped width of a given stem */
    723 
    724   static FT_Pos
    725   af_cjk_compute_stem_width( AF_GlyphHints  hints,
    726                              AF_Dimension   dim,
    727                              FT_Pos         width,
    728                              AF_Edge_Flags  base_flags,
    729                              AF_Edge_Flags  stem_flags )
    730   {
    731     AF_LatinMetrics  metrics  = (AF_LatinMetrics) hints->metrics;
    732     AF_LatinAxis     axis     = & metrics->axis[dim];
    733     FT_Pos           dist     = width;
    734     FT_Int           sign     = 0;
    735     FT_Int           vertical = ( dim == AF_DIMENSION_VERT );
    736 
    737     FT_UNUSED( base_flags );
    738     FT_UNUSED( stem_flags );
    739 
    740 
    741     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
    742       return width;
    743 
    744     if ( dist < 0 )
    745     {
    746       dist = -width;
    747       sign = 1;
    748     }
    749 
    750     if ( (  vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
    751          ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
    752     {
    753       /* smooth hinting process: very lightly quantize the stem width */
    754 
    755       if ( axis->width_count > 0 )
    756       {
    757         if ( FT_ABS( dist - axis->widths[0].cur ) < 40 )
    758         {
    759           dist = axis->widths[0].cur;
    760           if ( dist < 48 )
    761             dist = 48;
    762 
    763           goto Done_Width;
    764         }
    765       }
    766 
    767       if ( dist < 54 )
    768         dist += ( 54 - dist ) / 2 ;
    769       else if ( dist < 3 * 64 )
    770       {
    771         FT_Pos  delta;
    772 
    773 
    774         delta  = dist & 63;
    775         dist  &= -64;
    776 
    777         if ( delta < 10 )
    778           dist += delta;
    779         else if ( delta < 22 )
    780           dist += 10;
    781         else if ( delta < 42 )
    782           dist += delta;
    783         else if ( delta < 54 )
    784           dist += 54;
    785         else
    786           dist += delta;
    787       }
    788     }
    789     else
    790     {
    791       /* strong hinting process: snap the stem width to integer pixels */
    792 
    793       dist = af_cjk_snap_width( axis->widths, axis->width_count, dist );
    794 
    795       if ( vertical )
    796       {
    797         /* in the case of vertical hinting, always round */
    798         /* the stem heights to integer pixels            */
    799 
    800         if ( dist >= 64 )
    801           dist = ( dist + 16 ) & ~63;
    802         else
    803           dist = 64;
    804       }
    805       else
    806       {
    807         if ( AF_LATIN_HINTS_DO_MONO( hints ) )
    808         {
    809           /* monochrome horizontal hinting: snap widths to integer pixels */
    810           /* with a different threshold                                   */
    811 
    812           if ( dist < 64 )
    813             dist = 64;
    814           else
    815             dist = ( dist + 32 ) & ~63;
    816         }
    817         else
    818         {
    819           /* for horizontal anti-aliased hinting, we adopt a more subtle */
    820           /* approach: we strengthen small stems, round stems whose size */
    821           /* is between 1 and 2 pixels to an integer, otherwise nothing  */
    822 
    823           if ( dist < 48 )
    824             dist = ( dist + 64 ) >> 1;
    825 
    826           else if ( dist < 128 )
    827             dist = ( dist + 22 ) & ~63;
    828           else
    829             /* round otherwise to prevent color fringes in LCD mode */
    830             dist = ( dist + 32 ) & ~63;
    831         }
    832       }
    833     }
    834 
    835   Done_Width:
    836     if ( sign )
    837       dist = -dist;
    838 
    839     return dist;
    840   }
    841 
    842 
    843   /* align one stem edge relative to the previous stem edge */
    844 
    845   static void
    846   af_cjk_align_linked_edge( AF_GlyphHints  hints,
    847                             AF_Dimension   dim,
    848                             AF_Edge        base_edge,
    849                             AF_Edge        stem_edge )
    850   {
    851     FT_Pos  dist = stem_edge->opos - base_edge->opos;
    852 
    853     FT_Pos  fitted_width = af_cjk_compute_stem_width(
    854                              hints, dim, dist,
    855                              (AF_Edge_Flags)base_edge->flags,
    856                              (AF_Edge_Flags)stem_edge->flags );
    857 
    858 
    859     stem_edge->pos = base_edge->pos + fitted_width;
    860   }
    861 
    862 
    863   static void
    864   af_cjk_align_serif_edge( AF_GlyphHints  hints,
    865                            AF_Edge        base,
    866                            AF_Edge        serif )
    867   {
    868     FT_UNUSED( hints );
    869 
    870     serif->pos = base->pos + ( serif->opos - base->opos );
    871   }
    872 
    873 
    874   /*************************************************************************/
    875   /*************************************************************************/
    876   /*************************************************************************/
    877   /****                                                                 ****/
    878   /****                    E D G E   H I N T I N G                      ****/
    879   /****                                                                 ****/
    880   /*************************************************************************/
    881   /*************************************************************************/
    882   /*************************************************************************/
    883 
    884 
    885 #define AF_LIGHT_MODE_MAX_HORZ_GAP    9
    886 #define AF_LIGHT_MODE_MAX_VERT_GAP   15
    887 #define AF_LIGHT_MODE_MAX_DELTA_ABS  14
    888 
    889 
    890   static FT_Pos
    891   af_hint_normal_stem( AF_GlyphHints  hints,
    892                        AF_Edge        edge,
    893                        AF_Edge        edge2,
    894                        FT_Pos         anchor,
    895                        AF_Dimension   dim )
    896   {
    897     FT_Pos  org_len, cur_len, org_center;
    898     FT_Pos  cur_pos1, cur_pos2;
    899     FT_Pos  d_off1, u_off1, d_off2, u_off2, delta;
    900     FT_Pos  offset;
    901     FT_Pos  threshold = 64;
    902 
    903 
    904     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
    905     {
    906       if ( ( edge->flags  & AF_EDGE_ROUND ) &&
    907            ( edge2->flags & AF_EDGE_ROUND ) )
    908       {
    909         if ( dim == AF_DIMENSION_VERT )
    910           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP;
    911         else
    912           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP;
    913       }
    914       else
    915       {
    916         if ( dim == AF_DIMENSION_VERT )
    917           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP / 3;
    918         else
    919           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP / 3;
    920       }
    921     }
    922 
    923     org_len    = edge2->opos - edge->opos;
    924     cur_len    = af_cjk_compute_stem_width( hints, dim, org_len,
    925                                             (AF_Edge_Flags)edge->flags,
    926                                             (AF_Edge_Flags)edge2->flags );
    927 
    928     org_center = ( edge->opos + edge2->opos ) / 2 + anchor;
    929     cur_pos1   = org_center - cur_len / 2;
    930     cur_pos2   = cur_pos1 + cur_len;
    931     d_off1     = cur_pos1 - FT_PIX_FLOOR( cur_pos1 );
    932     d_off2     = cur_pos2 - FT_PIX_FLOOR( cur_pos2 );
    933     u_off1     = 64 - d_off1;
    934     u_off2     = 64 - d_off2;
    935     delta      = 0;
    936 
    937 
    938     if ( d_off1 == 0 || d_off2 == 0 )
    939       goto Exit;
    940 
    941     if ( cur_len <= threshold )
    942     {
    943       if ( d_off2 < cur_len )
    944       {
    945         if ( u_off1 <= d_off2 )
    946           delta =  u_off1;
    947         else
    948           delta = -d_off2;
    949       }
    950 
    951       goto Exit;
    952     }
    953 
    954     if ( threshold < 64 )
    955     {
    956       if ( d_off1 >= threshold || u_off1 >= threshold ||
    957            d_off2 >= threshold || u_off2 >= threshold )
    958         goto Exit;
    959     }
    960 
    961     offset = cur_len % 64;
    962 
    963     if ( offset < 32 )
    964     {
    965       if ( u_off1 <= offset || d_off2 <= offset )
    966         goto Exit;
    967     }
    968     else
    969       offset = 64 - threshold;
    970 
    971     d_off1 = threshold - u_off1;
    972     u_off1 = u_off1    - offset;
    973     u_off2 = threshold - d_off2;
    974     d_off2 = d_off2    - offset;
    975 
    976     if ( d_off1 <= u_off1 )
    977       u_off1 = -d_off1;
    978 
    979     if ( d_off2 <= u_off2 )
    980       u_off2 = -d_off2;
    981 
    982     if ( FT_ABS( u_off1 ) <= FT_ABS( u_off2 ) )
    983       delta = u_off1;
    984     else
    985       delta = u_off2;
    986 
    987   Exit:
    988 
    989 #if 1
    990     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
    991     {
    992       if ( delta > AF_LIGHT_MODE_MAX_DELTA_ABS )
    993         delta = AF_LIGHT_MODE_MAX_DELTA_ABS;
    994       else if ( delta < -AF_LIGHT_MODE_MAX_DELTA_ABS )
    995         delta = -AF_LIGHT_MODE_MAX_DELTA_ABS;
    996     }
    997 #endif
    998 
    999     cur_pos1 += delta;
   1000 
   1001     if ( edge->opos < edge2->opos )
   1002     {
   1003       edge->pos  = cur_pos1;
   1004       edge2->pos = cur_pos1 + cur_len;
   1005     }
   1006     else
   1007     {
   1008       edge->pos  = cur_pos1 + cur_len;
   1009       edge2->pos = cur_pos1;
   1010     }
   1011 
   1012     return delta;
   1013   }
   1014 
   1015 
   1016   static void
   1017   af_cjk_hint_edges( AF_GlyphHints  hints,
   1018                      AF_Dimension   dim )
   1019   {
   1020     AF_AxisHints  axis       = &hints->axis[dim];
   1021     AF_Edge       edges      = axis->edges;
   1022     AF_Edge       edge_limit = edges + axis->num_edges;
   1023     FT_PtrDist    n_edges;
   1024     AF_Edge       edge;
   1025     AF_Edge       anchor   = 0;
   1026     FT_Pos        delta    = 0;
   1027     FT_Int        skipped  = 0;
   1028 
   1029 
   1030     /* now we align all stem edges. */
   1031     for ( edge = edges; edge < edge_limit; edge++ )
   1032     {
   1033       AF_Edge  edge2;
   1034 
   1035 
   1036       if ( edge->flags & AF_EDGE_DONE )
   1037         continue;
   1038 
   1039       /* skip all non-stem edges */
   1040       edge2 = edge->link;
   1041       if ( !edge2 )
   1042       {
   1043         skipped++;
   1044         continue;
   1045       }
   1046 
   1047       /* now align the stem */
   1048 
   1049       if ( edge2 < edge )
   1050       {
   1051         af_cjk_align_linked_edge( hints, dim, edge2, edge );
   1052         edge->flags |= AF_EDGE_DONE;
   1053         continue;
   1054       }
   1055 
   1056       if ( dim != AF_DIMENSION_VERT && !anchor )
   1057       {
   1058 
   1059 #if 0
   1060         if ( fixedpitch )
   1061         {
   1062           AF_Edge     left  = edge;
   1063           AF_Edge     right = edge_limit - 1;
   1064           AF_EdgeRec  left1, left2, right1, right2;
   1065           FT_Pos      target, center1, center2;
   1066           FT_Pos      delta1, delta2, d1, d2;
   1067 
   1068 
   1069           while ( right > left && !right->link )
   1070             right--;
   1071 
   1072           left1  = *left;
   1073           left2  = *left->link;
   1074           right1 = *right->link;
   1075           right2 = *right;
   1076 
   1077           delta  = ( ( ( hinter->pp2.x + 32 ) & -64 ) - hinter->pp2.x ) / 2;
   1078           target = left->opos + ( right->opos - left->opos ) / 2 + delta - 16;
   1079 
   1080           delta1  = delta;
   1081           delta1 += af_hint_normal_stem( hints, left, left->link,
   1082                                          delta1, 0 );
   1083 
   1084           if ( left->link != right )
   1085             af_hint_normal_stem( hints, right->link, right, delta1, 0 );
   1086 
   1087           center1 = left->pos + ( right->pos - left->pos ) / 2;
   1088 
   1089           if ( center1 >= target )
   1090             delta2 = delta - 32;
   1091           else
   1092             delta2 = delta + 32;
   1093 
   1094           delta2 += af_hint_normal_stem( hints, &left1, &left2, delta2, 0 );
   1095 
   1096           if ( delta1 != delta2 )
   1097           {
   1098             if ( left->link != right )
   1099               af_hint_normal_stem( hints, &right1, &right2, delta2, 0 );
   1100 
   1101             center2 = left1.pos + ( right2.pos - left1.pos ) / 2;
   1102 
   1103             d1 = center1 - target;
   1104             d2 = center2 - target;
   1105 
   1106             if ( FT_ABS( d2 ) < FT_ABS( d1 ) )
   1107             {
   1108               left->pos       = left1.pos;
   1109               left->link->pos = left2.pos;
   1110 
   1111               if ( left->link != right )
   1112               {
   1113                 right->link->pos = right1.pos;
   1114                 right->pos       = right2.pos;
   1115               }
   1116 
   1117               delta1 = delta2;
   1118             }
   1119           }
   1120 
   1121           delta               = delta1;
   1122           right->link->flags |= AF_EDGE_DONE;
   1123           right->flags       |= AF_EDGE_DONE;
   1124         }
   1125         else
   1126 
   1127 #endif /* 0 */
   1128 
   1129           delta = af_hint_normal_stem( hints, edge, edge2, 0,
   1130                                        AF_DIMENSION_HORZ );
   1131       }
   1132       else
   1133         af_hint_normal_stem( hints, edge, edge2, delta, dim );
   1134 
   1135 #if 0
   1136       printf( "stem (%d,%d) adjusted (%.1f,%.1f)\n",
   1137                edge - edges, edge2 - edges,
   1138                ( edge->pos - edge->opos ) / 64.0,
   1139                ( edge2->pos - edge2->opos ) / 64.0 );
   1140 #endif
   1141 
   1142       anchor = edge;
   1143       edge->flags  |= AF_EDGE_DONE;
   1144       edge2->flags |= AF_EDGE_DONE;
   1145     }
   1146 
   1147     /* make sure that lowercase m's maintain their symmetry */
   1148 
   1149     /* In general, lowercase m's have six vertical edges if they are sans */
   1150     /* serif, or twelve if they are with serifs.  This implementation is  */
   1151     /* based on that assumption, and seems to work very well with most    */
   1152     /* faces.  However, if for a certain face this assumption is not      */
   1153     /* true, the m is just rendered like before.  In addition, any stem   */
   1154     /* correction will only be applied to symmetrical glyphs (even if the */
   1155     /* glyph is not an m), so the potential for unwanted distortion is    */
   1156     /* relatively low.                                                    */
   1157 
   1158     /* We don't handle horizontal edges since we can't easily assure that */
   1159     /* the third (lowest) stem aligns with the base line; it might end up */
   1160     /* one pixel higher or lower.                                         */
   1161 
   1162     n_edges = edge_limit - edges;
   1163     if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
   1164     {
   1165       AF_Edge  edge1, edge2, edge3;
   1166       FT_Pos   dist1, dist2, span;
   1167 
   1168 
   1169       if ( n_edges == 6 )
   1170       {
   1171         edge1 = edges;
   1172         edge2 = edges + 2;
   1173         edge3 = edges + 4;
   1174       }
   1175       else
   1176       {
   1177         edge1 = edges + 1;
   1178         edge2 = edges + 5;
   1179         edge3 = edges + 9;
   1180       }
   1181 
   1182       dist1 = edge2->opos - edge1->opos;
   1183       dist2 = edge3->opos - edge2->opos;
   1184 
   1185       span = dist1 - dist2;
   1186       if ( span < 0 )
   1187         span = -span;
   1188 
   1189       if ( edge1->link == edge1 + 1 &&
   1190            edge2->link == edge2 + 1 &&
   1191            edge3->link == edge3 + 1 && span < 8 )
   1192       {
   1193         delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
   1194         edge3->pos -= delta;
   1195         if ( edge3->link )
   1196           edge3->link->pos -= delta;
   1197 
   1198         /* move the serifs along with the stem */
   1199         if ( n_edges == 12 )
   1200         {
   1201           ( edges + 8 )->pos -= delta;
   1202           ( edges + 11 )->pos -= delta;
   1203         }
   1204 
   1205         edge3->flags |= AF_EDGE_DONE;
   1206         if ( edge3->link )
   1207           edge3->link->flags |= AF_EDGE_DONE;
   1208       }
   1209     }
   1210 
   1211     if ( !skipped )
   1212       return;
   1213 
   1214     /*
   1215      *  now hint the remaining edges (serifs and single) in order
   1216      *  to complete our processing
   1217      */
   1218     for ( edge = edges; edge < edge_limit; edge++ )
   1219     {
   1220       if ( edge->flags & AF_EDGE_DONE )
   1221         continue;
   1222 
   1223       if ( edge->serif )
   1224       {
   1225         af_cjk_align_serif_edge( hints, edge->serif, edge );
   1226         edge->flags |= AF_EDGE_DONE;
   1227         skipped--;
   1228       }
   1229     }
   1230 
   1231     if ( !skipped )
   1232       return;
   1233 
   1234     for ( edge = edges; edge < edge_limit; edge++ )
   1235     {
   1236       AF_Edge  before, after;
   1237 
   1238 
   1239       if ( edge->flags & AF_EDGE_DONE )
   1240         continue;
   1241 
   1242       before = after = edge;
   1243 
   1244       while ( --before >= edges )
   1245         if ( before->flags & AF_EDGE_DONE )
   1246           break;
   1247 
   1248       while ( ++after < edge_limit )
   1249         if ( after->flags & AF_EDGE_DONE )
   1250           break;
   1251 
   1252       if ( before >= edges || after < edge_limit )
   1253       {
   1254         if ( before < edges )
   1255           af_cjk_align_serif_edge( hints, after, edge );
   1256         else if ( after >= edge_limit )
   1257           af_cjk_align_serif_edge( hints, before, edge );
   1258         else
   1259         {
   1260           if ( after->fpos == before->fpos )
   1261             edge->pos = before->pos;
   1262           else
   1263             edge->pos = before->pos +
   1264                         FT_MulDiv( edge->fpos - before->fpos,
   1265                                    after->pos - before->pos,
   1266                                    after->fpos - before->fpos );
   1267         }
   1268       }
   1269     }
   1270   }
   1271 
   1272 
   1273   static void
   1274   af_cjk_align_edge_points( AF_GlyphHints  hints,
   1275                             AF_Dimension   dim )
   1276   {
   1277     AF_AxisHints  axis       = & hints->axis[dim];
   1278     AF_Edge       edges      = axis->edges;
   1279     AF_Edge       edge_limit = edges + axis->num_edges;
   1280     AF_Edge       edge;
   1281     FT_Bool       snapping;
   1282 
   1283 
   1284     snapping = FT_BOOL( ( dim == AF_DIMENSION_HORZ             &&
   1285                           AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) )  ||
   1286                         ( dim == AF_DIMENSION_VERT             &&
   1287                           AF_LATIN_HINTS_DO_VERT_SNAP( hints ) )  );
   1288 
   1289     for ( edge = edges; edge < edge_limit; edge++ )
   1290     {
   1291       /* move the points of each segment     */
   1292       /* in each edge to the edge's position */
   1293       AF_Segment  seg = edge->first;
   1294 
   1295 
   1296       if ( snapping )
   1297       {
   1298         do
   1299         {
   1300           AF_Point  point = seg->first;
   1301 
   1302 
   1303           for (;;)
   1304           {
   1305             if ( dim == AF_DIMENSION_HORZ )
   1306             {
   1307               point->x      = edge->pos;
   1308               point->flags |= AF_FLAG_TOUCH_X;
   1309             }
   1310             else
   1311             {
   1312               point->y      = edge->pos;
   1313               point->flags |= AF_FLAG_TOUCH_Y;
   1314             }
   1315 
   1316             if ( point == seg->last )
   1317               break;
   1318 
   1319             point = point->next;
   1320           }
   1321 
   1322           seg = seg->edge_next;
   1323 
   1324         } while ( seg != edge->first );
   1325       }
   1326       else
   1327       {
   1328         FT_Pos  delta = edge->pos - edge->opos;
   1329 
   1330 
   1331         do
   1332         {
   1333           AF_Point  point = seg->first;
   1334 
   1335 
   1336           for (;;)
   1337           {
   1338             if ( dim == AF_DIMENSION_HORZ )
   1339             {
   1340               point->x     += delta;
   1341               point->flags |= AF_FLAG_TOUCH_X;
   1342             }
   1343             else
   1344             {
   1345               point->y     += delta;
   1346               point->flags |= AF_FLAG_TOUCH_Y;
   1347             }
   1348 
   1349             if ( point == seg->last )
   1350               break;
   1351 
   1352             point = point->next;
   1353           }
   1354 
   1355           seg = seg->edge_next;
   1356 
   1357         } while ( seg != edge->first );
   1358       }
   1359     }
   1360   }
   1361 
   1362 
   1363   FT_LOCAL_DEF( FT_Error )
   1364   af_cjk_hints_apply( AF_GlyphHints    hints,
   1365                       FT_Outline*      outline,
   1366                       AF_LatinMetrics  metrics )
   1367   {
   1368     FT_Error  error;
   1369     int       dim;
   1370 
   1371     FT_UNUSED( metrics );
   1372 
   1373 
   1374     error = af_glyph_hints_reload( hints, outline );
   1375     if ( error )
   1376       goto Exit;
   1377 
   1378     /* analyze glyph outline */
   1379     if ( AF_HINTS_DO_HORIZONTAL( hints ) )
   1380     {
   1381       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_HORZ );
   1382       if ( error )
   1383         goto Exit;
   1384     }
   1385 
   1386     if ( AF_HINTS_DO_VERTICAL( hints ) )
   1387     {
   1388       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_VERT );
   1389       if ( error )
   1390         goto Exit;
   1391     }
   1392 
   1393     /* grid-fit the outline */
   1394     for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
   1395     {
   1396       if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) ||
   1397            ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) )   )
   1398       {
   1399 
   1400 #ifdef AF_USE_WARPER
   1401         if ( dim == AF_DIMENSION_HORZ                                  &&
   1402              metrics->root.scaler.render_mode == FT_RENDER_MODE_NORMAL )
   1403         {
   1404           AF_WarperRec  warper;
   1405           FT_Fixed      scale;
   1406           FT_Pos        delta;
   1407 
   1408 
   1409           af_warper_compute( &warper, hints, dim, &scale, &delta );
   1410           af_glyph_hints_scale_dim( hints, dim, scale, delta );
   1411           continue;
   1412         }
   1413 #endif /* AF_USE_WARPER */
   1414 
   1415         af_cjk_hint_edges( hints, (AF_Dimension)dim );
   1416         af_cjk_align_edge_points( hints, (AF_Dimension)dim );
   1417         af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
   1418         af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
   1419       }
   1420     }
   1421 
   1422 #if 0
   1423     af_glyph_hints_dump_points( hints );
   1424     af_glyph_hints_dump_segments( hints );
   1425     af_glyph_hints_dump_edges( hints );
   1426 #endif
   1427 
   1428     af_glyph_hints_save( hints, outline );
   1429 
   1430   Exit:
   1431     return error;
   1432   }
   1433 
   1434 
   1435   /*************************************************************************/
   1436   /*************************************************************************/
   1437   /*****                                                               *****/
   1438   /*****                C J K   S C R I P T   C L A S S                *****/
   1439   /*****                                                               *****/
   1440   /*************************************************************************/
   1441   /*************************************************************************/
   1442 
   1443 
   1444   static const AF_Script_UniRangeRec  af_cjk_uniranges[] =
   1445   {
   1446 #if 0
   1447     AF_UNIRANGE_REC(  0x0100UL,  0xFFFFUL ),  /* why this? */
   1448 #endif
   1449     AF_UNIRANGE_REC(  0x2E80UL,  0x2EFFUL ),  /* CJK Radicals Supplement                 */
   1450     AF_UNIRANGE_REC(  0x2F00UL,  0x2FDFUL ),  /* Kangxi Radicals                         */
   1451     AF_UNIRANGE_REC(  0x3000UL,  0x303FUL ),  /* CJK Symbols and Punctuation             */
   1452     AF_UNIRANGE_REC(  0x3040UL,  0x309FUL ),  /* Hiragana                                */
   1453     AF_UNIRANGE_REC(  0x30A0UL,  0x30FFUL ),  /* Katakana                                */
   1454     AF_UNIRANGE_REC(  0x3100UL,  0x312FUL ),  /* Bopomofo                                */
   1455     AF_UNIRANGE_REC(  0x3130UL,  0x318FUL ),  /* Hangul Compatibility Jamo               */
   1456     AF_UNIRANGE_REC(  0x31A0UL,  0x31BFUL ),  /* Bopomofo Extended                       */
   1457     AF_UNIRANGE_REC(  0x31C0UL,  0x31EFUL ),  /* CJK Strokes                             */
   1458     AF_UNIRANGE_REC(  0x31F0UL,  0x31FFUL ),  /* Katakana Phonetic Extensions            */
   1459     AF_UNIRANGE_REC(  0x3200UL,  0x32FFUL ),  /* Enclosed CJK Letters and Months         */
   1460     AF_UNIRANGE_REC(  0x3300UL,  0x33FFUL ),  /* CJK Compatibility                       */
   1461     AF_UNIRANGE_REC(  0x3400UL,  0x4DBFUL ),  /* CJK Unified Ideographs Extension A      */
   1462     AF_UNIRANGE_REC(  0x4DC0UL,  0x4DFFUL ),  /* Yijing Hexagram Symbols                 */
   1463     AF_UNIRANGE_REC(  0x4E00UL,  0x9FFFUL ),  /* CJK Unified Ideographs                  */
   1464     AF_UNIRANGE_REC(  0xF900UL,  0xFAFFUL ),  /* CJK Compatibility Ideographs            */
   1465     AF_UNIRANGE_REC(  0xFE30UL,  0xFE4FUL ),  /* CJK Compatibility Forms                 */
   1466     AF_UNIRANGE_REC(  0xFF00UL,  0xFFEFUL ),  /* Halfwidth and Fullwidth Forms           */
   1467     AF_UNIRANGE_REC( 0x20000UL, 0x2A6DFUL ),  /* CJK Unified Ideographs Extension B      */
   1468     AF_UNIRANGE_REC( 0x2F800UL, 0x2FA1FUL ),  /* CJK Compatibility Ideographs Supplement */
   1469     AF_UNIRANGE_REC(       0UL,       0UL )
   1470   };
   1471 
   1472 
   1473   AF_DEFINE_SCRIPT_CLASS(af_cjk_script_class,
   1474     AF_SCRIPT_CJK,
   1475     af_cjk_uniranges,
   1476 
   1477     sizeof( AF_LatinMetricsRec ),
   1478 
   1479     (AF_Script_InitMetricsFunc) af_cjk_metrics_init,
   1480     (AF_Script_ScaleMetricsFunc)af_cjk_metrics_scale,
   1481     (AF_Script_DoneMetricsFunc) NULL,
   1482 
   1483     (AF_Script_InitHintsFunc)   af_cjk_hints_init,
   1484     (AF_Script_ApplyHintsFunc)  af_cjk_hints_apply
   1485   )
   1486 
   1487 #else /* !AF_CONFIG_OPTION_CJK */
   1488 
   1489   static const AF_Script_UniRangeRec  af_cjk_uniranges[] =
   1490   {
   1491     AF_UNIRANGE_REC( 0UL, 0UL )
   1492   };
   1493 
   1494 
   1495   AF_DEFINE_SCRIPT_CLASS(af_cjk_script_class,
   1496     AF_SCRIPT_CJK,
   1497     af_cjk_uniranges,
   1498 
   1499     sizeof( AF_LatinMetricsRec ),
   1500 
   1501     (AF_Script_InitMetricsFunc) NULL,
   1502     (AF_Script_ScaleMetricsFunc)NULL,
   1503     (AF_Script_DoneMetricsFunc) NULL,
   1504 
   1505     (AF_Script_InitHintsFunc)   NULL,
   1506     (AF_Script_ApplyHintsFunc)  NULL
   1507   )
   1508 
   1509 #endif /* !AF_CONFIG_OPTION_CJK */
   1510 
   1511 
   1512 /* END */
   1513