tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

pshalgo.c (58934B)


      1 /****************************************************************************
      2 *
      3 * pshalgo.c
      4 *
      5 *   PostScript hinting algorithm (body).
      6 *
      7 * Copyright (C) 2001-2025 by
      8 * David Turner, Robert Wilhelm, and Werner Lemberg.
      9 *
     10 * This file is part of the FreeType project, and may only be used
     11 * modified and distributed under the terms of the FreeType project
     12 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
     13 * this file you indicate that you have read the license and
     14 * understand and accept it fully.
     15 *
     16 */
     17 
     18 
     19 #include <freetype/internal/ftobjs.h>
     20 #include <freetype/internal/ftdebug.h>
     21 #include <freetype/internal/ftcalc.h>
     22 #include "pshalgo.h"
     23 
     24 #include "pshnterr.h"
     25 
     26 
     27 #undef  FT_COMPONENT
     28 #define FT_COMPONENT  pshalgo
     29 
     30 
     31 #ifdef DEBUG_HINTER
     32  PSH_Hint_Table  ps_debug_hint_table = NULL;
     33  PSH_HintFunc    ps_debug_hint_func  = NULL;
     34  PSH_Glyph       ps_debug_glyph      = NULL;
     35 #endif
     36 
     37 
     38 #define  COMPUTE_INFLEXS  /* compute inflection points to optimize `S' */
     39                          /* and similar glyphs                        */
     40 
     41 
     42  /*************************************************************************/
     43  /*************************************************************************/
     44  /*****                                                               *****/
     45  /*****                  BASIC HINTS RECORDINGS                       *****/
     46  /*****                                                               *****/
     47  /*************************************************************************/
     48  /*************************************************************************/
     49 
     50  /* return true if two stem hints overlap */
     51  static FT_Int
     52  psh_hint_overlap( PSH_Hint  hint1,
     53                    PSH_Hint  hint2 )
     54  {
     55    return ADD_INT( hint1->org_pos, hint1->org_len ) >= hint2->org_pos &&
     56           ADD_INT( hint2->org_pos, hint2->org_len ) >= hint1->org_pos;
     57  }
     58 
     59 
     60  /* destroy hints table */
     61  static void
     62  psh_hint_table_done( PSH_Hint_Table  table,
     63                       FT_Memory       memory )
     64  {
     65    FT_FREE( table->zones );
     66    table->num_zones = 0;
     67    table->zone      = NULL;
     68 
     69    FT_FREE( table->sort );
     70    FT_FREE( table->hints );
     71    table->num_hints   = 0;
     72    table->max_hints   = 0;
     73    table->sort_global = NULL;
     74  }
     75 
     76 
     77  /* deactivate all hints in a table */
     78  static void
     79  psh_hint_table_deactivate( PSH_Hint_Table  table )
     80  {
     81    FT_UInt   count = table->max_hints;
     82    PSH_Hint  hint  = table->hints;
     83 
     84 
     85    for ( ; count > 0; count--, hint++ )
     86    {
     87      psh_hint_deactivate( hint );
     88      hint->order = -1;
     89    }
     90  }
     91 
     92 
     93  /* internal function to record a new hint */
     94  static void
     95  psh_hint_table_record( PSH_Hint_Table  table,
     96                         FT_UInt         idx )
     97  {
     98    PSH_Hint  hint = table->hints + idx;
     99 
    100 
    101    if ( idx >= table->max_hints )
    102    {
    103      FT_TRACE0(( "psh_hint_table_record: invalid hint index %u\n", idx ));
    104      return;
    105    }
    106 
    107    /* ignore active hints */
    108    if ( psh_hint_is_active( hint ) )
    109      return;
    110 
    111    psh_hint_activate( hint );
    112 
    113    /* now scan the current active hint set to check */
    114    /* whether `hint' overlaps with another hint     */
    115    {
    116      PSH_Hint*  sorted = table->sort_global;
    117      FT_UInt    count  = table->num_hints;
    118      PSH_Hint   hint2;
    119 
    120 
    121      hint->parent = NULL;
    122      for ( ; count > 0; count--, sorted++ )
    123      {
    124        hint2 = sorted[0];
    125 
    126        if ( psh_hint_overlap( hint, hint2 ) )
    127        {
    128          hint->parent = hint2;
    129          break;
    130        }
    131      }
    132    }
    133 
    134    if ( table->num_hints < table->max_hints )
    135      table->sort_global[table->num_hints++] = hint;
    136    else
    137      FT_TRACE0(( "psh_hint_table_record: too many sorted hints!  BUG!\n" ));
    138  }
    139 
    140 
    141  static void
    142  psh_hint_table_record_mask( PSH_Hint_Table  table,
    143                              PS_Mask         hint_mask )
    144  {
    145    FT_Int    mask = 0, val = 0;
    146    FT_Byte*  cursor = hint_mask->bytes;
    147    FT_UInt   idx, limit;
    148 
    149 
    150    limit = hint_mask->num_bits;
    151 
    152    for ( idx = 0; idx < limit; idx++ )
    153    {
    154      if ( mask == 0 )
    155      {
    156        val  = *cursor++;
    157        mask = 0x80;
    158      }
    159 
    160      if ( val & mask )
    161        psh_hint_table_record( table, idx );
    162 
    163      mask >>= 1;
    164    }
    165  }
    166 
    167 
    168  /* create hints table */
    169  static FT_Error
    170  psh_hint_table_init( PSH_Hint_Table  table,
    171                       PS_Hint_Table   hints,
    172                       PS_Mask_Table   hint_masks,
    173                       PS_Mask_Table   counter_masks,
    174                       FT_Memory       memory )
    175  {
    176    FT_UInt   count;
    177    FT_Error  error;
    178 
    179    FT_UNUSED( counter_masks );
    180 
    181 
    182    count = hints->num_hints;
    183 
    184    /* allocate our tables */
    185    if ( FT_QNEW_ARRAY( table->sort,  2 * count     ) ||
    186         FT_QNEW_ARRAY( table->hints,     count     ) ||
    187         FT_QNEW_ARRAY( table->zones, 2 * count + 1 ) )
    188      goto Exit;
    189 
    190    table->max_hints   = count;
    191    table->sort_global = FT_OFFSET( table->sort, count );
    192    table->num_hints   = 0;
    193    table->num_zones   = 0;
    194    table->zone        = NULL;
    195 
    196    /* initialize the `table->hints' array */
    197    {
    198      PSH_Hint  write = table->hints;
    199      PS_Hint   read  = hints->hints;
    200 
    201 
    202      for ( ; count > 0; count--, write++, read++ )
    203      {
    204        write->org_pos = read->pos;
    205        write->org_len = read->len;
    206        write->flags   = read->flags;
    207      }
    208    }
    209 
    210    /* we now need to determine the initial `parent' stems; first  */
    211    /* activate the hints that are given by the initial hint masks */
    212    if ( hint_masks )
    213    {
    214      PS_Mask  mask = hint_masks->masks;
    215 
    216 
    217      count             = hint_masks->num_masks;
    218      table->hint_masks = hint_masks;
    219 
    220      for ( ; count > 0; count--, mask++ )
    221        psh_hint_table_record_mask( table, mask );
    222    }
    223 
    224    /* finally, do a linear parse in case some hints were left alone */
    225    if ( table->num_hints != table->max_hints )
    226    {
    227      FT_UInt  idx;
    228 
    229 
    230      FT_TRACE0(( "psh_hint_table_init: missing/incorrect hint masks\n" ));
    231 
    232      count = table->max_hints;
    233      for ( idx = 0; idx < count; idx++ )
    234        psh_hint_table_record( table, idx );
    235    }
    236 
    237  Exit:
    238    return error;
    239  }
    240 
    241 
    242  static void
    243  psh_hint_table_activate_mask( PSH_Hint_Table  table,
    244                                PS_Mask         hint_mask )
    245  {
    246    FT_Int    mask = 0, val = 0;
    247    FT_Byte*  cursor = hint_mask->bytes;
    248    FT_UInt   idx, limit, count;
    249 
    250 
    251    limit = hint_mask->num_bits;
    252    count = 0;
    253 
    254    psh_hint_table_deactivate( table );
    255 
    256    for ( idx = 0; idx < limit; idx++ )
    257    {
    258      if ( mask == 0 )
    259      {
    260        val  = *cursor++;
    261        mask = 0x80;
    262      }
    263 
    264      if ( val & mask )
    265      {
    266        PSH_Hint  hint = &table->hints[idx];
    267 
    268 
    269        if ( !psh_hint_is_active( hint ) )
    270        {
    271          FT_UInt     count2;
    272 
    273 #if 0
    274          PSH_Hint*  sort = table->sort;
    275          PSH_Hint   hint2;
    276 
    277 
    278          for ( count2 = count; count2 > 0; count2--, sort++ )
    279          {
    280            hint2 = sort[0];
    281            if ( psh_hint_overlap( hint, hint2 ) )
    282              FT_TRACE0(( "psh_hint_table_activate_mask:"
    283                          " found overlapping hints\n" ))
    284          }
    285 #else
    286          count2 = 0;
    287 #endif
    288 
    289          if ( count2 == 0 )
    290          {
    291            psh_hint_activate( hint );
    292            if ( count < table->max_hints )
    293              table->sort[count++] = hint;
    294            else
    295              FT_TRACE0(( "psh_hint_tableactivate_mask:"
    296                          " too many active hints\n" ));
    297          }
    298        }
    299      }
    300 
    301      mask >>= 1;
    302    }
    303    table->num_hints = count;
    304 
    305    /* now, sort the hints; they are guaranteed to not overlap */
    306    /* so we can compare their "org_pos" field directly        */
    307    {
    308      FT_UInt    i1, i2;
    309      PSH_Hint   hint1, hint2;
    310      PSH_Hint*  sort = table->sort;
    311 
    312 
    313      /* a simple bubble sort will do, since in 99% of cases, the hints */
    314      /* will be already sorted -- and the sort will be linear          */
    315      for ( i1 = 1; i1 < count; i1++ )
    316      {
    317        hint1 = sort[i1];
    318        /* this loop stops when i2 wraps around after reaching 0 */
    319        for ( i2 = i1 - 1; i2 < i1; i2-- )
    320        {
    321          hint2 = sort[i2];
    322 
    323          if ( hint2->org_pos < hint1->org_pos )
    324            break;
    325 
    326          sort[i2 + 1] = hint2;
    327          sort[i2]     = hint1;
    328        }
    329      }
    330    }
    331  }
    332 
    333 
    334  /*************************************************************************/
    335  /*************************************************************************/
    336  /*****                                                               *****/
    337  /*****               HINTS GRID-FITTING AND OPTIMIZATION             *****/
    338  /*****                                                               *****/
    339  /*************************************************************************/
    340  /*************************************************************************/
    341 
    342 #if 1
    343  static FT_Pos
    344  psh_dimension_quantize_len( PSH_Dimension  dim,
    345                              FT_Pos         len,
    346                              FT_Bool        do_snapping )
    347  {
    348    if ( len <= 64 )
    349      len = 64;
    350    else
    351    {
    352      FT_Pos  delta = len - dim->stdw.widths[0].cur;
    353 
    354 
    355      if ( delta < 0 )
    356        delta = -delta;
    357 
    358      if ( delta < 40 )
    359      {
    360        len = dim->stdw.widths[0].cur;
    361        if ( len < 48 )
    362          len = 48;
    363      }
    364 
    365      if ( len < 3 * 64 )
    366      {
    367        delta = ( len & 63 );
    368        len  &= -64;
    369 
    370        if ( delta < 10 )
    371          len += delta;
    372 
    373        else if ( delta < 32 )
    374          len += 10;
    375 
    376        else if ( delta < 54 )
    377          len += 54;
    378 
    379        else
    380          len += delta;
    381      }
    382      else
    383        len = FT_PIX_ROUND( len );
    384    }
    385 
    386    if ( do_snapping )
    387      len = FT_PIX_ROUND( len );
    388 
    389    return  len;
    390  }
    391 #endif /* 0 */
    392 
    393 
    394 #ifdef DEBUG_HINTER
    395 
    396  static void
    397  ps_simple_scale( PSH_Hint_Table  table,
    398                   FT_Fixed        scale,
    399                   FT_Fixed        delta,
    400                   FT_Int          dimension )
    401  {
    402    FT_UInt  count;
    403 
    404 
    405    for ( count = 0; count < table->max_hints; count++ )
    406    {
    407      PSH_Hint  hint = table->hints + count;
    408 
    409 
    410      hint->cur_pos = FT_MulFix( hint->org_pos, scale ) + delta;
    411      hint->cur_len = FT_MulFix( hint->org_len, scale );
    412 
    413      if ( ps_debug_hint_func )
    414        ps_debug_hint_func( hint, dimension );
    415    }
    416  }
    417 
    418 #endif /* DEBUG_HINTER */
    419 
    420 
    421  static FT_Fixed
    422  psh_hint_snap_stem_side_delta( FT_Fixed  pos,
    423                                 FT_Fixed  len )
    424  {
    425    FT_Fixed  delta1 = FT_PIX_ROUND( pos ) - pos;
    426    FT_Fixed  delta2 = FT_PIX_ROUND( pos + len ) - pos - len;
    427 
    428 
    429    if ( FT_ABS( delta1 ) <= FT_ABS( delta2 ) )
    430      return delta1;
    431    else
    432      return delta2;
    433  }
    434 
    435 
    436  static void
    437  psh_hint_align( PSH_Hint     hint,
    438                  PSH_Globals  globals,
    439                  FT_Int       dimension,
    440                  PSH_Glyph    glyph )
    441  {
    442    PSH_Dimension  dim   = &globals->dimension[dimension];
    443    FT_Fixed       scale = dim->scale_mult;
    444    FT_Fixed       delta = dim->scale_delta;
    445 
    446 
    447    if ( !psh_hint_is_fitted( hint ) )
    448    {
    449      FT_Pos  pos = FT_MulFix( hint->org_pos, scale ) + delta;
    450      FT_Pos  len = FT_MulFix( hint->org_len, scale );
    451 
    452      FT_Int            do_snapping;
    453      FT_Pos            fit_len;
    454      PSH_AlignmentRec  align;
    455 
    456 
    457      /* ignore stem alignments when requested through the hint flags */
    458      if ( ( dimension == 0 && !glyph->do_horz_hints ) ||
    459           ( dimension == 1 && !glyph->do_vert_hints ) )
    460      {
    461        hint->cur_pos = pos;
    462        hint->cur_len = len;
    463 
    464        psh_hint_set_fitted( hint );
    465        return;
    466      }
    467 
    468      /* perform stem snapping when requested - this is necessary
    469       * for monochrome and LCD hinting modes only
    470       */
    471      do_snapping = ( dimension == 0 && glyph->do_horz_snapping ) ||
    472                    ( dimension == 1 && glyph->do_vert_snapping );
    473 
    474      hint->cur_len = fit_len = len;
    475 
    476      /* check blue zones for horizontal stems */
    477      align.align     = PSH_BLUE_ALIGN_NONE;
    478      align.align_bot = align.align_top = 0;
    479 
    480      if ( dimension == 1 )
    481        psh_blues_snap_stem( &globals->blues,
    482                             ADD_INT( hint->org_pos, hint->org_len ),
    483                             hint->org_pos,
    484                             &align );
    485 
    486      switch ( align.align )
    487      {
    488      case PSH_BLUE_ALIGN_TOP:
    489        /* the top of the stem is aligned against a blue zone */
    490        hint->cur_pos = align.align_top - fit_len;
    491        break;
    492 
    493      case PSH_BLUE_ALIGN_BOT:
    494        /* the bottom of the stem is aligned against a blue zone */
    495        hint->cur_pos = align.align_bot;
    496        break;
    497 
    498      case PSH_BLUE_ALIGN_TOP | PSH_BLUE_ALIGN_BOT:
    499        /* both edges of the stem are aligned against blue zones */
    500        hint->cur_pos = align.align_bot;
    501        hint->cur_len = align.align_top - align.align_bot;
    502        break;
    503 
    504      default:
    505        {
    506          PSH_Hint  parent = hint->parent;
    507 
    508 
    509          if ( parent )
    510          {
    511            FT_Pos  par_org_center, par_cur_center;
    512            FT_Pos  cur_org_center, cur_delta;
    513 
    514 
    515            /* ensure that parent is already fitted */
    516            if ( !psh_hint_is_fitted( parent ) )
    517              psh_hint_align( parent, globals, dimension, glyph );
    518 
    519            /* keep original relation between hints, that is, use the */
    520            /* scaled distance between the centers of the hints to    */
    521            /* compute the new position                               */
    522            par_org_center = parent->org_pos + ( parent->org_len >> 1 );
    523            par_cur_center = parent->cur_pos + ( parent->cur_len >> 1 );
    524            cur_org_center = hint->org_pos   + ( hint->org_len   >> 1 );
    525 
    526            cur_delta = FT_MulFix( cur_org_center - par_org_center, scale );
    527            pos       = par_cur_center + cur_delta - ( len >> 1 );
    528          }
    529 
    530          hint->cur_pos = pos;
    531          hint->cur_len = fit_len;
    532 
    533          /* Stem adjustment tries to snap stem widths to standard
    534           * ones.  This is important to prevent unpleasant rounding
    535           * artefacts.
    536           */
    537          if ( glyph->do_stem_adjust )
    538          {
    539            if ( len <= 64 )
    540            {
    541              /* the stem is less than one pixel; we will center it
    542               * around the nearest pixel center
    543               */
    544              if ( len >= 32 )
    545              {
    546                /* This is a special case where we also widen the stem
    547                 * and align it to the pixel grid.
    548                 *
    549                 *   stem_center          = pos + (len/2)
    550                 *   nearest_pixel_center = FT_ROUND(stem_center-32)+32
    551                 *   new_pos              = nearest_pixel_center-32
    552                 *                        = FT_ROUND(stem_center-32)
    553                 *                        = FT_FLOOR(stem_center-32+32)
    554                 *                        = FT_FLOOR(stem_center)
    555                 *   new_len              = 64
    556                 */
    557                pos = FT_PIX_FLOOR( pos + ( len >> 1 ) );
    558                len = 64;
    559              }
    560              else if ( len > 0 )
    561              {
    562                /* This is a very small stem; we simply align it to the
    563                 * pixel grid, trying to find the minimum displacement.
    564                 *
    565                 * left               = pos
    566                 * right              = pos + len
    567                 * left_nearest_edge  = ROUND(pos)
    568                 * right_nearest_edge = ROUND(right)
    569                 *
    570                 * if ( ABS(left_nearest_edge - left) <=
    571                 *      ABS(right_nearest_edge - right) )
    572                 *    new_pos = left
    573                 * else
    574                 *    new_pos = right
    575                 */
    576                FT_Pos  left_nearest  = FT_PIX_ROUND( pos );
    577                FT_Pos  right_nearest = FT_PIX_ROUND( pos + len );
    578                FT_Pos  left_disp     = left_nearest - pos;
    579                FT_Pos  right_disp    = right_nearest - ( pos + len );
    580 
    581 
    582                if ( left_disp < 0 )
    583                  left_disp = -left_disp;
    584                if ( right_disp < 0 )
    585                  right_disp = -right_disp;
    586                if ( left_disp <= right_disp )
    587                  pos = left_nearest;
    588                else
    589                  pos = right_nearest;
    590              }
    591              else
    592              {
    593                /* this is a ghost stem; we simply round it */
    594                pos = FT_PIX_ROUND( pos );
    595              }
    596            }
    597            else
    598            {
    599              len = psh_dimension_quantize_len( dim, len, 0 );
    600            }
    601          }
    602 
    603          /* now that we have a good hinted stem width, try to position */
    604          /* the stem along a pixel grid integer coordinate             */
    605          hint->cur_pos = pos + psh_hint_snap_stem_side_delta( pos, len );
    606          hint->cur_len = len;
    607        }
    608      }
    609 
    610      if ( do_snapping )
    611      {
    612        pos = hint->cur_pos;
    613        len = hint->cur_len;
    614 
    615        if ( len < 64 )
    616          len = 64;
    617        else
    618          len = FT_PIX_ROUND( len );
    619 
    620        switch ( align.align )
    621        {
    622          case PSH_BLUE_ALIGN_TOP:
    623            hint->cur_pos = align.align_top - len;
    624            hint->cur_len = len;
    625            break;
    626 
    627          case PSH_BLUE_ALIGN_BOT:
    628            hint->cur_len = len;
    629            break;
    630 
    631          case PSH_BLUE_ALIGN_BOT | PSH_BLUE_ALIGN_TOP:
    632            /* don't touch */
    633            break;
    634 
    635 
    636          default:
    637            hint->cur_len = len;
    638            if ( len & 64 )
    639              pos = FT_PIX_FLOOR( pos + ( len >> 1 ) ) + 32;
    640            else
    641              pos = FT_PIX_ROUND( pos + ( len >> 1 ) );
    642 
    643            hint->cur_pos = pos - ( len >> 1 );
    644            hint->cur_len = len;
    645        }
    646      }
    647 
    648      psh_hint_set_fitted( hint );
    649 
    650 #ifdef DEBUG_HINTER
    651      if ( ps_debug_hint_func )
    652        ps_debug_hint_func( hint, dimension );
    653 #endif
    654    }
    655  }
    656 
    657 
    658 #if 0  /* not used for now, experimental */
    659 
    660 /*
    661  * A variant to perform "light" hinting (i.e. FT_RENDER_MODE_LIGHT)
    662  * of stems
    663  */
    664  static void
    665  psh_hint_align_light( PSH_Hint     hint,
    666                        PSH_Globals  globals,
    667                        FT_Int       dimension,
    668                        PSH_Glyph    glyph )
    669  {
    670    PSH_Dimension  dim   = &globals->dimension[dimension];
    671    FT_Fixed       scale = dim->scale_mult;
    672    FT_Fixed       delta = dim->scale_delta;
    673 
    674 
    675    if ( !psh_hint_is_fitted( hint ) )
    676    {
    677      FT_Pos  pos = FT_MulFix( hint->org_pos, scale ) + delta;
    678      FT_Pos  len = FT_MulFix( hint->org_len, scale );
    679 
    680      FT_Pos  fit_len;
    681 
    682      PSH_AlignmentRec  align;
    683 
    684 
    685      /* ignore stem alignments when requested through the hint flags */
    686      if ( ( dimension == 0 && !glyph->do_horz_hints ) ||
    687           ( dimension == 1 && !glyph->do_vert_hints ) )
    688      {
    689        hint->cur_pos = pos;
    690        hint->cur_len = len;
    691 
    692        psh_hint_set_fitted( hint );
    693        return;
    694      }
    695 
    696      fit_len = len;
    697 
    698      hint->cur_len = fit_len;
    699 
    700      /* check blue zones for horizontal stems */
    701      align.align = PSH_BLUE_ALIGN_NONE;
    702      align.align_bot = align.align_top = 0;
    703 
    704      if ( dimension == 1 )
    705        psh_blues_snap_stem( &globals->blues,
    706                             ADD_INT( hint->org_pos, hint->org_len ),
    707                             hint->org_pos,
    708                             &align );
    709 
    710      switch ( align.align )
    711      {
    712      case PSH_BLUE_ALIGN_TOP:
    713        /* the top of the stem is aligned against a blue zone */
    714        hint->cur_pos = align.align_top - fit_len;
    715        break;
    716 
    717      case PSH_BLUE_ALIGN_BOT:
    718        /* the bottom of the stem is aligned against a blue zone */
    719        hint->cur_pos = align.align_bot;
    720        break;
    721 
    722      case PSH_BLUE_ALIGN_TOP | PSH_BLUE_ALIGN_BOT:
    723        /* both edges of the stem are aligned against blue zones */
    724        hint->cur_pos = align.align_bot;
    725        hint->cur_len = align.align_top - align.align_bot;
    726        break;
    727 
    728      default:
    729        {
    730          PSH_Hint  parent = hint->parent;
    731 
    732 
    733          if ( parent )
    734          {
    735            FT_Pos  par_org_center, par_cur_center;
    736            FT_Pos  cur_org_center, cur_delta;
    737 
    738 
    739            /* ensure that parent is already fitted */
    740            if ( !psh_hint_is_fitted( parent ) )
    741              psh_hint_align_light( parent, globals, dimension, glyph );
    742 
    743            par_org_center = parent->org_pos + ( parent->org_len / 2 );
    744            par_cur_center = parent->cur_pos + ( parent->cur_len / 2 );
    745            cur_org_center = hint->org_pos   + ( hint->org_len   / 2 );
    746 
    747            cur_delta = FT_MulFix( cur_org_center - par_org_center, scale );
    748            pos       = par_cur_center + cur_delta - ( len >> 1 );
    749          }
    750 
    751          /* Stems less than one pixel wide are easy -- we want to
    752           * make them as dark as possible, so they must fall within
    753           * one pixel.  If the stem is split between two pixels
    754           * then snap the edge that is nearer to the pixel boundary
    755           * to the pixel boundary.
    756           */
    757          if ( len <= 64 )
    758          {
    759            if ( ( pos + len + 63 ) / 64  != pos / 64 + 1 )
    760              pos += psh_hint_snap_stem_side_delta ( pos, len );
    761          }
    762 
    763          /* Position stems other to minimize the amount of mid-grays.
    764           * There are, in general, two positions that do this,
    765           * illustrated as A) and B) below.
    766           *
    767           *   +                   +                   +                   +
    768           *
    769           * A)             |--------------------------------|
    770           * B)   |--------------------------------|
    771           * C)       |--------------------------------|
    772           *
    773           * Position A) (split the excess stem equally) should be better
    774           * for stems of width N + f where f < 0.5.
    775           *
    776           * Position B) (split the deficiency equally) should be better
    777           * for stems of width N + f where f > 0.5.
    778           *
    779           * It turns out though that minimizing the total number of lit
    780           * pixels is also important, so position C), with one edge
    781           * aligned with a pixel boundary is actually preferable
    782           * to A).  There are also more possible positions for C) than
    783           * for A) or B), so it involves less distortion of the overall
    784           * character shape.
    785           */
    786          else /* len > 64 */
    787          {
    788            FT_Fixed  frac_len = len & 63;
    789            FT_Fixed  center = pos + ( len >> 1 );
    790            FT_Fixed  delta_a, delta_b;
    791 
    792 
    793            if ( ( len / 64 ) & 1 )
    794            {
    795              delta_a = FT_PIX_FLOOR( center ) + 32 - center;
    796              delta_b = FT_PIX_ROUND( center ) - center;
    797            }
    798            else
    799            {
    800              delta_a = FT_PIX_ROUND( center ) - center;
    801              delta_b = FT_PIX_FLOOR( center ) + 32 - center;
    802            }
    803 
    804            /* We choose between B) and C) above based on the amount
    805             * of fractional stem width; for small amounts, choose
    806             * C) always, for large amounts, B) always, and inbetween,
    807             * pick whichever one involves less stem movement.
    808             */
    809            if ( frac_len < 32 )
    810            {
    811              pos += psh_hint_snap_stem_side_delta ( pos, len );
    812            }
    813            else if ( frac_len < 48 )
    814            {
    815              FT_Fixed  side_delta = psh_hint_snap_stem_side_delta ( pos,
    816                                                                     len );
    817 
    818              if ( FT_ABS( side_delta ) < FT_ABS( delta_b ) )
    819                pos += side_delta;
    820              else
    821                pos += delta_b;
    822            }
    823            else
    824            {
    825              pos += delta_b;
    826            }
    827          }
    828 
    829          hint->cur_pos = pos;
    830        }
    831      }  /* switch */
    832 
    833      psh_hint_set_fitted( hint );
    834 
    835 #ifdef DEBUG_HINTER
    836      if ( ps_debug_hint_func )
    837        ps_debug_hint_func( hint, dimension );
    838 #endif
    839    }
    840  }
    841 
    842 #endif /* 0 */
    843 
    844 
    845  static void
    846  psh_hint_table_align_hints( PSH_Hint_Table  table,
    847                              PSH_Globals     globals,
    848                              FT_Int          dimension,
    849                              PSH_Glyph       glyph )
    850  {
    851    PSH_Hint       hint;
    852    FT_UInt        count;
    853 
    854 #ifdef DEBUG_HINTER
    855 
    856    PSH_Dimension  dim   = &globals->dimension[dimension];
    857    FT_Fixed       scale = dim->scale_mult;
    858    FT_Fixed       delta = dim->scale_delta;
    859 
    860 
    861    if ( ps_debug_no_vert_hints && dimension == 0 )
    862    {
    863      ps_simple_scale( table, scale, delta, dimension );
    864      return;
    865    }
    866 
    867    if ( ps_debug_no_horz_hints && dimension == 1 )
    868    {
    869      ps_simple_scale( table, scale, delta, dimension );
    870      return;
    871    }
    872 
    873 #endif /* DEBUG_HINTER */
    874 
    875    hint  = table->hints;
    876    count = table->max_hints;
    877 
    878    for ( ; count > 0; count--, hint++ )
    879      psh_hint_align( hint, globals, dimension, glyph );
    880  }
    881 
    882 
    883  /*************************************************************************/
    884  /*************************************************************************/
    885  /*****                                                               *****/
    886  /*****                POINTS INTERPOLATION ROUTINES                  *****/
    887  /*****                                                               *****/
    888  /*************************************************************************/
    889  /*************************************************************************/
    890 
    891 #define xxDEBUG_ZONES
    892 
    893 
    894 #ifdef DEBUG_ZONES
    895 
    896 #include FT_CONFIG_STANDARD_LIBRARY_H
    897 
    898  static void
    899  psh_print_zone( PSH_Zone  zone )
    900  {
    901    printf( "zone [scale,delta,min,max] = [%.5f,%.2f,%d,%d]\n",
    902             zone->scale / 65536.0,
    903             zone->delta / 64.0,
    904             zone->min,
    905             zone->max );
    906  }
    907 
    908 #endif /* DEBUG_ZONES */
    909 
    910 
    911  /*************************************************************************/
    912  /*************************************************************************/
    913  /*****                                                               *****/
    914  /*****                    HINTER GLYPH MANAGEMENT                    *****/
    915  /*****                                                               *****/
    916  /*************************************************************************/
    917  /*************************************************************************/
    918 
    919 #define  psh_corner_is_flat      ft_corner_is_flat
    920 #define  psh_corner_orientation  ft_corner_orientation
    921 
    922 
    923 #ifdef COMPUTE_INFLEXS
    924 
    925  /* compute all inflex points in a given glyph */
    926  static void
    927  psh_glyph_compute_inflections( PSH_Glyph  glyph )
    928  {
    929    FT_UInt  n;
    930 
    931 
    932    for ( n = 0; n < glyph->num_contours; n++ )
    933    {
    934      PSH_Point  first, start, end, before, after;
    935      FT_Pos     in_x, in_y, out_x, out_y;
    936      FT_Int     orient_prev, orient_cur;
    937      FT_Int     finished = 0;
    938 
    939 
    940      /* we need at least 4 points to create an inflection point */
    941      if ( glyph->contours[n].count < 4 )
    942        continue;
    943 
    944      /* compute first segment in contour */
    945      first = glyph->contours[n].start;
    946 
    947      start = end = first;
    948      do
    949      {
    950        end = end->next;
    951        if ( end == first )
    952          goto Skip;
    953 
    954        in_x = end->org_u - start->org_u;
    955        in_y = end->org_v - start->org_v;
    956 
    957      } while ( in_x == 0 && in_y == 0 );
    958 
    959      /* extend the segment start whenever possible */
    960      before = start;
    961      do
    962      {
    963        do
    964        {
    965          start  = before;
    966          before = before->prev;
    967          if ( before == first )
    968            goto Skip;
    969 
    970          out_x = start->org_u - before->org_u;
    971          out_y = start->org_v - before->org_v;
    972 
    973        } while ( out_x == 0 && out_y == 0 );
    974 
    975        orient_prev = psh_corner_orientation( in_x, in_y, out_x, out_y );
    976 
    977      } while ( orient_prev == 0 );
    978 
    979      first = start;
    980      in_x  = out_x;
    981      in_y  = out_y;
    982 
    983      /* now, process all segments in the contour */
    984      do
    985      {
    986        /* first, extend current segment's end whenever possible */
    987        after = end;
    988        do
    989        {
    990          do
    991          {
    992            end   = after;
    993            after = after->next;
    994            if ( after == first )
    995              finished = 1;
    996 
    997            out_x = after->org_u - end->org_u;
    998            out_y = after->org_v - end->org_v;
    999 
   1000          } while ( out_x == 0 && out_y == 0 );
   1001 
   1002          orient_cur = psh_corner_orientation( in_x, in_y, out_x, out_y );
   1003 
   1004        } while ( orient_cur == 0 );
   1005 
   1006        if ( ( orient_cur ^ orient_prev ) < 0 )
   1007        {
   1008          do
   1009          {
   1010            psh_point_set_inflex( start );
   1011            start = start->next;
   1012          }
   1013          while ( start != end );
   1014 
   1015          psh_point_set_inflex( start );
   1016        }
   1017 
   1018        start       = end;
   1019        end         = after;
   1020        orient_prev = orient_cur;
   1021        in_x        = out_x;
   1022        in_y        = out_y;
   1023 
   1024      } while ( !finished );
   1025 
   1026    Skip:
   1027      ;
   1028    }
   1029  }
   1030 
   1031 #endif /* COMPUTE_INFLEXS */
   1032 
   1033 
   1034  static void
   1035  psh_glyph_done( PSH_Glyph  glyph )
   1036  {
   1037    FT_Memory  memory = glyph->memory;
   1038 
   1039 
   1040    psh_hint_table_done( &glyph->hint_tables[1], memory );
   1041    psh_hint_table_done( &glyph->hint_tables[0], memory );
   1042 
   1043    FT_FREE( glyph->points );
   1044    FT_FREE( glyph->contours );
   1045 
   1046    glyph->num_points   = 0;
   1047    glyph->num_contours = 0;
   1048 
   1049    glyph->memory = NULL;
   1050  }
   1051 
   1052 
   1053  static PSH_Dir
   1054  psh_compute_dir( FT_Pos  dx,
   1055                   FT_Pos  dy )
   1056  {
   1057    FT_Pos   ax, ay;
   1058    PSH_Dir  result = PSH_DIR_NONE;
   1059 
   1060 
   1061    ax = FT_ABS( dx );
   1062    ay = FT_ABS( dy );
   1063 
   1064    if ( ay * 12 < ax )
   1065    {
   1066      /* |dy| <<< |dx|  means a near-horizontal segment */
   1067      result = ( dx >= 0 ) ? PSH_DIR_RIGHT : PSH_DIR_LEFT;
   1068    }
   1069    else if ( ax * 12 < ay )
   1070    {
   1071      /* |dx| <<< |dy|  means a near-vertical segment */
   1072      result = ( dy >= 0 ) ? PSH_DIR_UP : PSH_DIR_DOWN;
   1073    }
   1074 
   1075    return result;
   1076  }
   1077 
   1078 
   1079  /* load outline point coordinates into hinter glyph */
   1080  static void
   1081  psh_glyph_load_points( PSH_Glyph  glyph,
   1082                         FT_Int     dimension )
   1083  {
   1084    FT_Vector*  vec   = glyph->outline->points;
   1085    PSH_Point   point = glyph->points;
   1086    FT_UInt     count = glyph->num_points;
   1087 
   1088 
   1089    for ( ; count > 0; count--, point++, vec++ )
   1090    {
   1091      point->flags2 = 0;
   1092      point->hint   = NULL;
   1093      if ( dimension == 0 )
   1094      {
   1095        point->org_u = vec->x;
   1096        point->org_v = vec->y;
   1097      }
   1098      else
   1099      {
   1100        point->org_u = vec->y;
   1101        point->org_v = vec->x;
   1102      }
   1103 
   1104 #ifdef DEBUG_HINTER
   1105      point->org_x = vec->x;
   1106      point->org_y = vec->y;
   1107 #endif
   1108 
   1109    }
   1110  }
   1111 
   1112 
   1113  /* save hinted point coordinates back to outline */
   1114  static void
   1115  psh_glyph_save_points( PSH_Glyph  glyph,
   1116                         FT_Int     dimension )
   1117  {
   1118    FT_UInt     n;
   1119    PSH_Point   point = glyph->points;
   1120    FT_Vector*  vec   = glyph->outline->points;
   1121    FT_Byte*    tags  = glyph->outline->tags;
   1122 
   1123 
   1124    for ( n = 0; n < glyph->num_points; n++ )
   1125    {
   1126      if ( dimension == 0 )
   1127        vec[n].x = point->cur_u;
   1128      else
   1129        vec[n].y = point->cur_u;
   1130 
   1131      if ( psh_point_is_strong( point ) )
   1132        tags[n] |= (char)( ( dimension == 0 ) ? 32 : 64 );
   1133 
   1134 #ifdef DEBUG_HINTER
   1135 
   1136      if ( dimension == 0 )
   1137      {
   1138        point->cur_x   = point->cur_u;
   1139        point->flags_x = point->flags2 | point->flags;
   1140      }
   1141      else
   1142      {
   1143        point->cur_y   = point->cur_u;
   1144        point->flags_y = point->flags2 | point->flags;
   1145      }
   1146 
   1147 #endif
   1148 
   1149      point++;
   1150    }
   1151  }
   1152 
   1153 
   1154  static FT_Error
   1155  psh_glyph_init( PSH_Glyph    glyph,
   1156                  FT_Outline*  outline,
   1157                  PS_Hints     ps_hints,
   1158                  PSH_Globals  globals )
   1159  {
   1160    FT_Error   error;
   1161    FT_Memory  memory;
   1162 
   1163 
   1164    /* clear all fields */
   1165    FT_ZERO( glyph );
   1166 
   1167    memory = glyph->memory = globals->memory;
   1168 
   1169    /* allocate and setup points + contours arrays */
   1170    if ( FT_QNEW_ARRAY( glyph->points,   outline->n_points   ) ||
   1171         FT_QNEW_ARRAY( glyph->contours, outline->n_contours ) )
   1172      goto Exit;
   1173 
   1174    glyph->num_points   = outline->n_points;
   1175    glyph->num_contours = outline->n_contours;
   1176 
   1177    {
   1178      FT_UInt      first = 0, next, n;
   1179      PSH_Point    points  = glyph->points;
   1180      PSH_Contour  contour = glyph->contours;
   1181 
   1182 
   1183      for ( n = 0; n < glyph->num_contours; n++ )
   1184      {
   1185        FT_UInt    count;
   1186        PSH_Point  point;
   1187 
   1188 
   1189        next  = outline->contours[n] + 1;
   1190        count = next - first;
   1191 
   1192        contour->start = points + first;
   1193        contour->count = count;
   1194 
   1195        if ( count > 0 )
   1196        {
   1197          point = points + first;
   1198 
   1199          point->prev    = points + next - 1;
   1200          point->contour = contour;
   1201 
   1202          for ( ; count > 1; count-- )
   1203          {
   1204            point[0].next = point + 1;
   1205            point[1].prev = point;
   1206            point++;
   1207            point->contour = contour;
   1208          }
   1209          point->next = points + first;
   1210        }
   1211 
   1212        contour++;
   1213        first = next;
   1214      }
   1215    }
   1216 
   1217    {
   1218      PSH_Point   points = glyph->points;
   1219      PSH_Point   point  = points;
   1220      FT_Vector*  vec    = outline->points;
   1221      FT_UInt     n;
   1222 
   1223 
   1224      for ( n = 0; n < glyph->num_points; n++, point++ )
   1225      {
   1226        FT_Int  n_prev = (FT_Int)( point->prev - points );
   1227        FT_Int  n_next = (FT_Int)( point->next - points );
   1228        FT_Pos  dxi, dyi, dxo, dyo;
   1229 
   1230 
   1231        point->flags = 0;
   1232        if ( !( outline->tags[n] & FT_CURVE_TAG_ON ) )
   1233          psh_point_set_off( point );
   1234 
   1235        dxi = vec[n].x - vec[n_prev].x;
   1236        dyi = vec[n].y - vec[n_prev].y;
   1237 
   1238        point->dir_in = psh_compute_dir( dxi, dyi );
   1239 
   1240        dxo = vec[n_next].x - vec[n].x;
   1241        dyo = vec[n_next].y - vec[n].y;
   1242 
   1243        point->dir_out = psh_compute_dir( dxo, dyo );
   1244 
   1245        /* detect smooth points */
   1246        if ( psh_point_is_off( point ) )
   1247          psh_point_set_smooth( point );
   1248 
   1249        else if ( point->dir_in == point->dir_out )
   1250        {
   1251          if ( point->dir_out != PSH_DIR_NONE           ||
   1252               psh_corner_is_flat( dxi, dyi, dxo, dyo ) )
   1253            psh_point_set_smooth( point );
   1254        }
   1255      }
   1256    }
   1257 
   1258    glyph->outline = outline;
   1259    glyph->globals = globals;
   1260 
   1261 #ifdef COMPUTE_INFLEXS
   1262    psh_glyph_load_points( glyph, 0 );
   1263    psh_glyph_compute_inflections( glyph );
   1264 #endif /* COMPUTE_INFLEXS */
   1265 
   1266    /* now deal with hints tables */
   1267    error = psh_hint_table_init( &glyph->hint_tables [0],
   1268                                 &ps_hints->dimension[0].hints,
   1269                                 &ps_hints->dimension[0].masks,
   1270                                 &ps_hints->dimension[0].counters,
   1271                                 memory );
   1272    if ( error )
   1273      goto Exit;
   1274 
   1275    error = psh_hint_table_init( &glyph->hint_tables [1],
   1276                                 &ps_hints->dimension[1].hints,
   1277                                 &ps_hints->dimension[1].masks,
   1278                                 &ps_hints->dimension[1].counters,
   1279                                 memory );
   1280    if ( error )
   1281      goto Exit;
   1282 
   1283  Exit:
   1284    return error;
   1285  }
   1286 
   1287 
   1288  /* compute all extrema in a glyph for a given dimension */
   1289  static void
   1290  psh_glyph_compute_extrema( PSH_Glyph  glyph )
   1291  {
   1292    FT_UInt  n;
   1293 
   1294 
   1295    /* first of all, compute all local extrema */
   1296    for ( n = 0; n < glyph->num_contours; n++ )
   1297    {
   1298      PSH_Point  first = glyph->contours[n].start;
   1299      PSH_Point  point, before, after;
   1300 
   1301 
   1302      if ( glyph->contours[n].count == 0 )
   1303        continue;
   1304 
   1305      point  = first;
   1306      before = point;
   1307 
   1308      do
   1309      {
   1310        before = before->prev;
   1311        if ( before == first )
   1312          goto Skip;
   1313 
   1314      } while ( before->org_u == point->org_u );
   1315 
   1316      first = point = before->next;
   1317 
   1318      for (;;)
   1319      {
   1320        after = point;
   1321        do
   1322        {
   1323          after = after->next;
   1324          if ( after == first )
   1325            goto Next;
   1326 
   1327        } while ( after->org_u == point->org_u );
   1328 
   1329        if ( before->org_u < point->org_u )
   1330        {
   1331          if ( after->org_u < point->org_u )
   1332          {
   1333            /* local maximum */
   1334            goto Extremum;
   1335          }
   1336        }
   1337        else /* before->org_u > point->org_u */
   1338        {
   1339          if ( after->org_u > point->org_u )
   1340          {
   1341            /* local minimum */
   1342          Extremum:
   1343            do
   1344            {
   1345              psh_point_set_extremum( point );
   1346              point = point->next;
   1347 
   1348            } while ( point != after );
   1349          }
   1350        }
   1351 
   1352        before = after->prev;
   1353        point  = after;
   1354 
   1355      } /* for  */
   1356 
   1357    Next:
   1358      ;
   1359    }
   1360 
   1361    /* for each extremum, determine its direction along the */
   1362    /* orthogonal axis                                      */
   1363    for ( n = 0; n < glyph->num_points; n++ )
   1364    {
   1365      PSH_Point  point, before, after;
   1366 
   1367 
   1368      point  = &glyph->points[n];
   1369      before = point;
   1370      after  = point;
   1371 
   1372      if ( psh_point_is_extremum( point ) )
   1373      {
   1374        do
   1375        {
   1376          before = before->prev;
   1377          if ( before == point )
   1378            goto Skip;
   1379 
   1380        } while ( before->org_v == point->org_v );
   1381 
   1382        do
   1383        {
   1384          after = after->next;
   1385          if ( after == point )
   1386            goto Skip;
   1387 
   1388        } while ( after->org_v == point->org_v );
   1389      }
   1390 
   1391      if ( before->org_v < point->org_v &&
   1392           after->org_v  > point->org_v )
   1393      {
   1394        psh_point_set_positive( point );
   1395      }
   1396      else if ( before->org_v > point->org_v &&
   1397                after->org_v  < point->org_v )
   1398      {
   1399        psh_point_set_negative( point );
   1400      }
   1401 
   1402    Skip:
   1403      ;
   1404    }
   1405  }
   1406 
   1407 
   1408  /* the min and max are based on contour orientation and fill rule */
   1409  static void
   1410  psh_hint_table_find_strong_points( PSH_Hint_Table  table,
   1411                                     PSH_Point       point,
   1412                                     FT_UInt         count,
   1413                                     FT_Int          threshold,
   1414                                     PSH_Dir         major_dir )
   1415  {
   1416    PSH_Hint*  sort      = table->sort;
   1417    FT_UInt    num_hints = table->num_hints;
   1418 
   1419 
   1420    for ( ; count > 0; count--, point++ )
   1421    {
   1422      PSH_Dir  point_dir;
   1423      FT_Pos   org_u = point->org_u;
   1424 
   1425 
   1426      if ( psh_point_is_strong( point ) )
   1427        continue;
   1428 
   1429      point_dir =
   1430        (PSH_Dir)( ( point->dir_in | point->dir_out ) & major_dir );
   1431 
   1432      if ( point_dir & ( PSH_DIR_DOWN | PSH_DIR_RIGHT ) )
   1433      {
   1434        FT_UInt  nn;
   1435 
   1436 
   1437        for ( nn = 0; nn < num_hints; nn++ )
   1438        {
   1439          PSH_Hint  hint = sort[nn];
   1440          FT_Pos    d    = org_u - hint->org_pos;
   1441 
   1442 
   1443          if ( d < threshold && -d < threshold )
   1444          {
   1445            psh_point_set_strong( point );
   1446            point->flags2 |= PSH_POINT_EDGE_MIN;
   1447            point->hint    = hint;
   1448            break;
   1449          }
   1450        }
   1451      }
   1452      else if ( point_dir & ( PSH_DIR_UP | PSH_DIR_LEFT ) )
   1453      {
   1454        FT_UInt  nn;
   1455 
   1456 
   1457        for ( nn = 0; nn < num_hints; nn++ )
   1458        {
   1459          PSH_Hint  hint = sort[nn];
   1460          FT_Pos    d    = org_u - hint->org_pos - hint->org_len;
   1461 
   1462 
   1463          if ( d < threshold && -d < threshold )
   1464          {
   1465            psh_point_set_strong( point );
   1466            point->flags2 |= PSH_POINT_EDGE_MAX;
   1467            point->hint    = hint;
   1468            break;
   1469          }
   1470        }
   1471      }
   1472 
   1473 #if 1
   1474      else if ( psh_point_is_extremum( point ) )
   1475      {
   1476        /* treat extrema as special cases for stem edge alignment */
   1477        FT_UInt  nn, min_flag, max_flag;
   1478 
   1479 
   1480        if ( major_dir == PSH_DIR_HORIZONTAL )
   1481        {
   1482          min_flag = PSH_POINT_POSITIVE;
   1483          max_flag = PSH_POINT_NEGATIVE;
   1484        }
   1485        else
   1486        {
   1487          min_flag = PSH_POINT_NEGATIVE;
   1488          max_flag = PSH_POINT_POSITIVE;
   1489        }
   1490 
   1491        if ( point->flags2 & min_flag )
   1492        {
   1493          for ( nn = 0; nn < num_hints; nn++ )
   1494          {
   1495            PSH_Hint  hint = sort[nn];
   1496            FT_Pos    d    = org_u - hint->org_pos;
   1497 
   1498 
   1499            if ( d < threshold && -d < threshold )
   1500            {
   1501              point->flags2 |= PSH_POINT_EDGE_MIN;
   1502              point->hint    = hint;
   1503              psh_point_set_strong( point );
   1504              break;
   1505            }
   1506          }
   1507        }
   1508        else if ( point->flags2 & max_flag )
   1509        {
   1510          for ( nn = 0; nn < num_hints; nn++ )
   1511          {
   1512            PSH_Hint  hint = sort[nn];
   1513            FT_Pos    d    = org_u - hint->org_pos - hint->org_len;
   1514 
   1515 
   1516            if ( d < threshold && -d < threshold )
   1517            {
   1518              point->flags2 |= PSH_POINT_EDGE_MAX;
   1519              point->hint    = hint;
   1520              psh_point_set_strong( point );
   1521              break;
   1522            }
   1523          }
   1524        }
   1525 
   1526        if ( !point->hint )
   1527        {
   1528          for ( nn = 0; nn < num_hints; nn++ )
   1529          {
   1530            PSH_Hint  hint = sort[nn];
   1531 
   1532 
   1533            if ( org_u >=          hint->org_pos                  &&
   1534                 org_u <= ADD_INT( hint->org_pos, hint->org_len ) )
   1535            {
   1536              point->hint = hint;
   1537              break;
   1538            }
   1539          }
   1540        }
   1541      }
   1542 
   1543 #endif /* 1 */
   1544    }
   1545  }
   1546 
   1547 
   1548  /* the accepted shift for strong points in fractional pixels */
   1549 #define PSH_STRONG_THRESHOLD  32
   1550 
   1551  /* the maximum shift value in font units tuned to distinguish */
   1552  /* between stems and serifs in URW+ font collection           */
   1553 #define PSH_STRONG_THRESHOLD_MAXIMUM  12
   1554 
   1555 
   1556  /* find strong points in a glyph */
   1557  static void
   1558  psh_glyph_find_strong_points( PSH_Glyph  glyph,
   1559                                FT_Int     dimension )
   1560  {
   1561    /* a point is `strong' if it is located on a stem edge and       */
   1562    /* has an `in' or `out' tangent parallel to the hint's direction */
   1563 
   1564    PSH_Hint_Table  table     = &glyph->hint_tables[dimension];
   1565    PS_Mask         mask      = table->hint_masks->masks;
   1566    FT_UInt         num_masks = table->hint_masks->num_masks;
   1567    FT_UInt         first     = 0;
   1568    PSH_Dir         major_dir = ( dimension == 0 ) ? PSH_DIR_VERTICAL
   1569                                                   : PSH_DIR_HORIZONTAL;
   1570    PSH_Dimension   dim       = &glyph->globals->dimension[dimension];
   1571    FT_Fixed        scale     = dim->scale_mult;
   1572    FT_Int          threshold;
   1573 
   1574 
   1575    threshold = (FT_Int)FT_DivFix( PSH_STRONG_THRESHOLD, scale );
   1576    if ( threshold > PSH_STRONG_THRESHOLD_MAXIMUM )
   1577      threshold = PSH_STRONG_THRESHOLD_MAXIMUM;
   1578 
   1579    /* process secondary hints to `selected' points */
   1580    if ( num_masks > 1 && glyph->num_points > 0 )
   1581    {
   1582      /* the `endchar' op can reduce the number of points */
   1583      first = mask->end_point > glyph->num_points
   1584                ? glyph->num_points
   1585                : mask->end_point;
   1586      mask++;
   1587      for ( ; num_masks > 1; num_masks--, mask++ )
   1588      {
   1589        FT_UInt  next = FT_MIN( mask->end_point, glyph->num_points );
   1590 
   1591 
   1592        if ( next > first )
   1593        {
   1594          FT_UInt    count = next - first;
   1595          PSH_Point  point = glyph->points + first;
   1596 
   1597 
   1598          psh_hint_table_activate_mask( table, mask );
   1599 
   1600          psh_hint_table_find_strong_points( table, point, count,
   1601                                             threshold, major_dir );
   1602        }
   1603        first = next;
   1604      }
   1605    }
   1606 
   1607    /* process primary hints for all points */
   1608    if ( num_masks == 1 )
   1609    {
   1610      FT_UInt    count = glyph->num_points;
   1611      PSH_Point  point = glyph->points;
   1612 
   1613 
   1614      psh_hint_table_activate_mask( table, table->hint_masks->masks );
   1615 
   1616      psh_hint_table_find_strong_points( table, point, count,
   1617                                         threshold, major_dir );
   1618    }
   1619 
   1620    /* now, certain points may have been attached to a hint and */
   1621    /* not marked as strong; update their flags then            */
   1622    {
   1623      FT_UInt    count = glyph->num_points;
   1624      PSH_Point  point = glyph->points;
   1625 
   1626 
   1627      for ( ; count > 0; count--, point++ )
   1628        if ( point->hint && !psh_point_is_strong( point ) )
   1629          psh_point_set_strong( point );
   1630    }
   1631  }
   1632 
   1633 
   1634  /* find points in a glyph which are in a blue zone and have `in' or */
   1635  /* `out' tangents parallel to the horizontal axis                   */
   1636  static void
   1637  psh_glyph_find_blue_points( PSH_Blues  blues,
   1638                              PSH_Glyph  glyph )
   1639  {
   1640    PSH_Blue_Table  table;
   1641    PSH_Blue_Zone   zone;
   1642    FT_UInt         glyph_count = glyph->num_points;
   1643    FT_UInt         blue_count;
   1644    PSH_Point       point = glyph->points;
   1645 
   1646 
   1647    for ( ; glyph_count > 0; glyph_count--, point++ )
   1648    {
   1649      FT_Pos  y;
   1650 
   1651 
   1652      /* check tangents */
   1653      if ( !( point->dir_in  & PSH_DIR_HORIZONTAL ) &&
   1654           !( point->dir_out & PSH_DIR_HORIZONTAL ) )
   1655        continue;
   1656 
   1657      /* skip strong points */
   1658      if ( psh_point_is_strong( point ) )
   1659        continue;
   1660 
   1661      y = point->org_u;
   1662 
   1663      /* look up top zones */
   1664      table      = &blues->normal_top;
   1665      blue_count = table->count;
   1666      zone       = table->zones;
   1667 
   1668      for ( ; blue_count > 0; blue_count--, zone++ )
   1669      {
   1670        FT_Pos  delta = y - zone->org_bottom;
   1671 
   1672 
   1673        if ( delta < -blues->blue_fuzz )
   1674          break;
   1675 
   1676        if ( y <= zone->org_top + blues->blue_fuzz )
   1677          if ( blues->no_overshoots || delta <= blues->blue_threshold )
   1678          {
   1679            point->cur_u = zone->cur_bottom;
   1680            psh_point_set_strong( point );
   1681            psh_point_set_fitted( point );
   1682          }
   1683      }
   1684 
   1685      /* look up bottom zones */
   1686      table      = &blues->normal_bottom;
   1687      blue_count = table->count;
   1688      zone       = table->zones + blue_count - 1;
   1689 
   1690      for ( ; blue_count > 0; blue_count--, zone-- )
   1691      {
   1692        FT_Pos  delta = zone->org_top - y;
   1693 
   1694 
   1695        if ( delta < -blues->blue_fuzz )
   1696          break;
   1697 
   1698        if ( y >= zone->org_bottom - blues->blue_fuzz )
   1699          if ( blues->no_overshoots || delta < blues->blue_threshold )
   1700          {
   1701            point->cur_u = zone->cur_top;
   1702            psh_point_set_strong( point );
   1703            psh_point_set_fitted( point );
   1704          }
   1705      }
   1706    }
   1707  }
   1708 
   1709 
   1710  /* interpolate strong points with the help of hinted coordinates */
   1711  static void
   1712  psh_glyph_interpolate_strong_points( PSH_Glyph  glyph,
   1713                                       FT_Int     dimension )
   1714  {
   1715    PSH_Dimension  dim   = &glyph->globals->dimension[dimension];
   1716    FT_Fixed       scale = dim->scale_mult;
   1717 
   1718    FT_UInt        count = glyph->num_points;
   1719    PSH_Point      point = glyph->points;
   1720 
   1721 
   1722    for ( ; count > 0; count--, point++ )
   1723    {
   1724      PSH_Hint  hint = point->hint;
   1725 
   1726 
   1727      if ( hint )
   1728      {
   1729        FT_Pos  delta;
   1730 
   1731 
   1732        if ( psh_point_is_edge_min( point ) )
   1733          point->cur_u = hint->cur_pos;
   1734 
   1735        else if ( psh_point_is_edge_max( point ) )
   1736          point->cur_u = hint->cur_pos + hint->cur_len;
   1737 
   1738        else
   1739        {
   1740          delta = point->org_u - hint->org_pos;
   1741 
   1742          if ( delta <= 0 )
   1743            point->cur_u = hint->cur_pos + FT_MulFix( delta, scale );
   1744 
   1745          else if ( delta >= hint->org_len )
   1746            point->cur_u = hint->cur_pos + hint->cur_len +
   1747                             FT_MulFix( delta - hint->org_len, scale );
   1748 
   1749          else /* hint->org_len > 0 */
   1750            point->cur_u = hint->cur_pos +
   1751                             FT_MulDiv( delta, hint->cur_len,
   1752                                        hint->org_len );
   1753        }
   1754        psh_point_set_fitted( point );
   1755      }
   1756    }
   1757  }
   1758 
   1759 
   1760 #define  PSH_MAX_STRONG_INTERNAL  16
   1761 
   1762  static void
   1763  psh_glyph_interpolate_normal_points( PSH_Glyph  glyph,
   1764                                       FT_Int     dimension )
   1765  {
   1766 
   1767 #if 1
   1768    /* first technique: a point is strong if it is a local extremum */
   1769 
   1770    PSH_Dimension  dim    = &glyph->globals->dimension[dimension];
   1771    FT_Fixed       scale  = dim->scale_mult;
   1772    FT_Memory      memory = glyph->memory;
   1773 
   1774    PSH_Point*     strongs     = NULL;
   1775    PSH_Point      strongs_0[PSH_MAX_STRONG_INTERNAL];
   1776    FT_UInt        num_strongs = 0;
   1777 
   1778    PSH_Point      points = glyph->points;
   1779    PSH_Point      points_end = points + glyph->num_points;
   1780    PSH_Point      point;
   1781 
   1782 
   1783    /* first count the number of strong points */
   1784    for ( point = points; point < points_end; point++ )
   1785    {
   1786      if ( psh_point_is_strong( point ) )
   1787        num_strongs++;
   1788    }
   1789 
   1790    if ( num_strongs == 0 )  /* nothing to do here */
   1791      return;
   1792 
   1793    /* allocate an array to store a list of points, */
   1794    /* stored in increasing org_u order             */
   1795    if ( num_strongs <= PSH_MAX_STRONG_INTERNAL )
   1796      strongs = strongs_0;
   1797    else
   1798    {
   1799      FT_Error  error;
   1800 
   1801 
   1802      if ( FT_QNEW_ARRAY( strongs, num_strongs ) )
   1803        return;
   1804    }
   1805 
   1806    num_strongs = 0;
   1807    for ( point = points; point < points_end; point++ )
   1808    {
   1809      PSH_Point*  insert;
   1810 
   1811 
   1812      if ( !psh_point_is_strong( point ) )
   1813        continue;
   1814 
   1815      for ( insert = strongs + num_strongs; insert > strongs; insert-- )
   1816      {
   1817        if ( insert[-1]->org_u <= point->org_u )
   1818          break;
   1819 
   1820        insert[0] = insert[-1];
   1821      }
   1822      insert[0] = point;
   1823      num_strongs++;
   1824    }
   1825 
   1826    /* now try to interpolate all normal points */
   1827    for ( point = points; point < points_end; point++ )
   1828    {
   1829      if ( psh_point_is_strong( point ) )
   1830        continue;
   1831 
   1832      /* sometimes, some local extrema are smooth points */
   1833      if ( psh_point_is_smooth( point ) )
   1834      {
   1835        if ( point->dir_in == PSH_DIR_NONE   ||
   1836             point->dir_in != point->dir_out )
   1837          continue;
   1838 
   1839        if ( !psh_point_is_extremum( point ) &&
   1840             !psh_point_is_inflex( point )   )
   1841          continue;
   1842 
   1843        point->flags &= ~PSH_POINT_SMOOTH;
   1844      }
   1845 
   1846      /* find best enclosing point coordinates then interpolate */
   1847      {
   1848        PSH_Point   before, after;
   1849        FT_UInt     nn;
   1850 
   1851 
   1852        for ( nn = 0; nn < num_strongs; nn++ )
   1853          if ( strongs[nn]->org_u > point->org_u )
   1854            break;
   1855 
   1856        if ( nn == 0 )  /* point before the first strong point */
   1857        {
   1858          after = strongs[0];
   1859 
   1860          point->cur_u = after->cur_u +
   1861                           FT_MulFix( point->org_u - after->org_u,
   1862                                      scale );
   1863        }
   1864        else
   1865        {
   1866          before = strongs[nn - 1];
   1867 
   1868          for ( nn = num_strongs; nn > 0; nn-- )
   1869            if ( strongs[nn - 1]->org_u < point->org_u )
   1870              break;
   1871 
   1872          if ( nn == num_strongs )  /* point is after last strong point */
   1873          {
   1874            before = strongs[nn - 1];
   1875 
   1876            point->cur_u = before->cur_u +
   1877                             FT_MulFix( point->org_u - before->org_u,
   1878                                        scale );
   1879          }
   1880          else
   1881          {
   1882            FT_Pos  u;
   1883 
   1884 
   1885            after = strongs[nn];
   1886 
   1887            /* now interpolate point between before and after */
   1888            u = point->org_u;
   1889 
   1890            if ( u == before->org_u )
   1891              point->cur_u = before->cur_u;
   1892 
   1893            else if ( u == after->org_u )
   1894              point->cur_u = after->cur_u;
   1895 
   1896            else
   1897              point->cur_u = before->cur_u +
   1898                               FT_MulDiv( u - before->org_u,
   1899                                          after->cur_u - before->cur_u,
   1900                                          after->org_u - before->org_u );
   1901          }
   1902        }
   1903        psh_point_set_fitted( point );
   1904      }
   1905    }
   1906 
   1907    if ( strongs != strongs_0 )
   1908      FT_FREE( strongs );
   1909 
   1910 #endif /* 1 */
   1911 
   1912  }
   1913 
   1914 
   1915  /* interpolate other points */
   1916  static void
   1917  psh_glyph_interpolate_other_points( PSH_Glyph  glyph,
   1918                                      FT_Int     dimension )
   1919  {
   1920    PSH_Dimension  dim          = &glyph->globals->dimension[dimension];
   1921    FT_Fixed       scale        = dim->scale_mult;
   1922    FT_Fixed       delta        = dim->scale_delta;
   1923    PSH_Contour    contour      = glyph->contours;
   1924    FT_UInt        num_contours = glyph->num_contours;
   1925 
   1926 
   1927    for ( ; num_contours > 0; num_contours--, contour++ )
   1928    {
   1929      PSH_Point  start = contour->start;
   1930      PSH_Point  first, next, point;
   1931      FT_UInt    fit_count;
   1932 
   1933 
   1934      /* count the number of strong points in this contour */
   1935      next      = start + contour->count;
   1936      fit_count = 0;
   1937      first     = NULL;
   1938 
   1939      for ( point = start; point < next; point++ )
   1940        if ( psh_point_is_fitted( point ) )
   1941        {
   1942          if ( !first )
   1943            first = point;
   1944 
   1945          fit_count++;
   1946        }
   1947 
   1948      /* if there are less than 2 fitted points in the contour, we */
   1949      /* simply scale and eventually translate the contour points  */
   1950      if ( fit_count < 2 )
   1951      {
   1952        if ( fit_count == 1 )
   1953          delta = first->cur_u - FT_MulFix( first->org_u, scale );
   1954 
   1955        for ( point = start; point < next; point++ )
   1956          if ( point != first )
   1957            point->cur_u = FT_MulFix( point->org_u, scale ) + delta;
   1958 
   1959        goto Next_Contour;
   1960      }
   1961 
   1962      /* there are more than 2 strong points in this contour; we */
   1963      /* need to interpolate weak points between them            */
   1964      start = first;
   1965      do
   1966      {
   1967        /* skip consecutive fitted points */
   1968        for (;;)
   1969        {
   1970          next = first->next;
   1971          if ( next == start )
   1972            goto Next_Contour;
   1973 
   1974          if ( !psh_point_is_fitted( next ) )
   1975            break;
   1976 
   1977          first = next;
   1978        }
   1979 
   1980        /* find next fitted point after unfitted one */
   1981        for (;;)
   1982        {
   1983          next = next->next;
   1984          if ( psh_point_is_fitted( next ) )
   1985            break;
   1986        }
   1987 
   1988        /* now interpolate between them */
   1989        {
   1990          FT_Pos    org_a, org_ab, cur_a, cur_ab;
   1991          FT_Pos    org_c, org_ac, cur_c;
   1992          FT_Fixed  scale_ab;
   1993 
   1994 
   1995          if ( first->org_u <= next->org_u )
   1996          {
   1997            org_a  = first->org_u;
   1998            cur_a  = first->cur_u;
   1999            org_ab = next->org_u - org_a;
   2000            cur_ab = next->cur_u - cur_a;
   2001          }
   2002          else
   2003          {
   2004            org_a  = next->org_u;
   2005            cur_a  = next->cur_u;
   2006            org_ab = first->org_u - org_a;
   2007            cur_ab = first->cur_u - cur_a;
   2008          }
   2009 
   2010          scale_ab = 0x10000L;
   2011          if ( org_ab > 0 )
   2012            scale_ab = FT_DivFix( cur_ab, org_ab );
   2013 
   2014          point = first->next;
   2015          do
   2016          {
   2017            org_c  = point->org_u;
   2018            org_ac = org_c - org_a;
   2019 
   2020            if ( org_ac <= 0 )
   2021            {
   2022              /* on the left of the interpolation zone */
   2023              cur_c = cur_a + FT_MulFix( org_ac, scale );
   2024            }
   2025            else if ( org_ac >= org_ab )
   2026            {
   2027              /* on the right on the interpolation zone */
   2028              cur_c = cur_a + cur_ab + FT_MulFix( org_ac - org_ab, scale );
   2029            }
   2030            else
   2031            {
   2032              /* within the interpolation zone */
   2033              cur_c = cur_a + FT_MulFix( org_ac, scale_ab );
   2034            }
   2035 
   2036            point->cur_u = cur_c;
   2037 
   2038            point = point->next;
   2039 
   2040          } while ( point != next );
   2041        }
   2042 
   2043        /* keep going until all points in the contours have been processed */
   2044        first = next;
   2045 
   2046      } while ( first != start );
   2047 
   2048    Next_Contour:
   2049      ;
   2050    }
   2051  }
   2052 
   2053 
   2054  /*************************************************************************/
   2055  /*************************************************************************/
   2056  /*****                                                               *****/
   2057  /*****                     HIGH-LEVEL INTERFACE                      *****/
   2058  /*****                                                               *****/
   2059  /*************************************************************************/
   2060  /*************************************************************************/
   2061 
   2062  FT_Error
   2063  ps_hints_apply( PS_Hints        ps_hints,
   2064                  FT_Outline*     outline,
   2065                  PSH_Globals     globals,
   2066                  FT_Render_Mode  hint_mode )
   2067  {
   2068    PSH_GlyphRec  glyphrec;
   2069    PSH_Glyph     glyph = &glyphrec;
   2070    FT_Error      error;
   2071 #ifdef DEBUG_HINTER
   2072    FT_Memory     memory;
   2073 #endif
   2074    FT_Int        dimension;
   2075 
   2076 
   2077    /* something to do? */
   2078    if ( outline->n_points == 0 || outline->n_contours == 0 )
   2079      return FT_Err_Ok;
   2080 
   2081 #ifdef DEBUG_HINTER
   2082 
   2083    memory = globals->memory;
   2084 
   2085    if ( ps_debug_glyph )
   2086    {
   2087      psh_glyph_done( ps_debug_glyph );
   2088      FT_FREE( ps_debug_glyph );
   2089    }
   2090 
   2091    if ( FT_NEW( glyph ) )
   2092      return error;
   2093 
   2094    ps_debug_glyph = glyph;
   2095 
   2096 #endif /* DEBUG_HINTER */
   2097 
   2098    error = psh_glyph_init( glyph, outline, ps_hints, globals );
   2099    if ( error )
   2100      goto Exit;
   2101 
   2102    /* try to optimize the y_scale so that the top of non-capital letters
   2103     * is aligned on a pixel boundary whenever possible
   2104     */
   2105    {
   2106      PSH_Dimension  dim_x = &glyph->globals->dimension[0];
   2107      PSH_Dimension  dim_y = &glyph->globals->dimension[1];
   2108 
   2109      FT_Fixed  x_scale = dim_x->scale_mult;
   2110      FT_Fixed  y_scale = dim_y->scale_mult;
   2111 
   2112      FT_Fixed  old_x_scale = x_scale;
   2113      FT_Fixed  old_y_scale = y_scale;
   2114 
   2115      FT_Fixed  scaled = 0;
   2116      FT_Fixed  fitted = 0;
   2117 
   2118      FT_Bool  rescale = FALSE;
   2119 
   2120 
   2121      if ( globals->blues.normal_top.count )
   2122      {
   2123        scaled = FT_MulFix( globals->blues.normal_top.zones->org_ref, y_scale );
   2124        fitted = FT_PIX_ROUND( scaled );
   2125      }
   2126 
   2127      if ( fitted != 0 && scaled != fitted )
   2128      {
   2129        rescale = TRUE;
   2130 
   2131        y_scale = FT_MulDiv( y_scale, fitted, scaled );
   2132 
   2133        if ( fitted < scaled )
   2134          x_scale -= x_scale / 50;
   2135 
   2136        psh_globals_set_scale( glyph->globals, x_scale, y_scale, 0, 0 );
   2137      }
   2138 
   2139      glyph->do_horz_hints = 1;
   2140      glyph->do_vert_hints = 1;
   2141 
   2142      glyph->do_horz_snapping = FT_BOOL( hint_mode == FT_RENDER_MODE_MONO ||
   2143                                         hint_mode == FT_RENDER_MODE_LCD  );
   2144 
   2145      glyph->do_vert_snapping = FT_BOOL( hint_mode == FT_RENDER_MODE_MONO  ||
   2146                                         hint_mode == FT_RENDER_MODE_LCD_V );
   2147 
   2148      glyph->do_stem_adjust   = FT_BOOL( hint_mode != FT_RENDER_MODE_LIGHT );
   2149 
   2150      for ( dimension = 0; dimension < 2; dimension++ )
   2151      {
   2152        /* load outline coordinates into glyph */
   2153        psh_glyph_load_points( glyph, dimension );
   2154 
   2155        /* compute local extrema */
   2156        psh_glyph_compute_extrema( glyph );
   2157 
   2158        /* compute aligned stem/hints positions */
   2159        psh_hint_table_align_hints( &glyph->hint_tables[dimension],
   2160                                    glyph->globals,
   2161                                    dimension,
   2162                                    glyph );
   2163 
   2164        /* find strong points, align them, then interpolate others */
   2165        psh_glyph_find_strong_points( glyph, dimension );
   2166        if ( dimension == 1 )
   2167          psh_glyph_find_blue_points( &globals->blues, glyph );
   2168        psh_glyph_interpolate_strong_points( glyph, dimension );
   2169        psh_glyph_interpolate_normal_points( glyph, dimension );
   2170        psh_glyph_interpolate_other_points( glyph, dimension );
   2171 
   2172        /* save hinted coordinates back to outline */
   2173        psh_glyph_save_points( glyph, dimension );
   2174 
   2175        if ( rescale )
   2176          psh_globals_set_scale( glyph->globals,
   2177                                 old_x_scale, old_y_scale, 0, 0 );
   2178      }
   2179    }
   2180 
   2181  Exit:
   2182 
   2183 #ifndef DEBUG_HINTER
   2184    psh_glyph_done( glyph );
   2185 #endif
   2186 
   2187    return error;
   2188  }
   2189 
   2190 
   2191 /* END */