shithub: freetype+ttf2subf

ref: c49f69cb8c9d9b38179f760bf0cbbe4c78a3e296
dir: /src/autohint/ahhint.c/

View raw version
/***************************************************************************/
/*                                                                         */
/*  ahhint.c                                                               */
/*                                                                         */
/*    Glyph hinter                                                         */
/*                                                                         */
/*  Copyright 2000: Catharon Productions Inc.                              */
/*  Author: David Turner                                                   */
/*                                                                         */
/*  This file is part of the Catharon Typography Project and shall only    */
/*  be used, modified, and distributed under the terms of the Catharon     */
/*  Open Source License that should come with this file under the name     */
/*  "CatharonLicense.txt". By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/*  Note that this license is compatible with the FreeType license         */
/*                                                                         */
/***************************************************************************/

#ifdef FT_FLAT_COMPILE
#include "ahhint.h"
#include "ahglyph.h"
#include "ahangles.h"
#else
#include <autohint/ahhint.h>
#include <autohint/ahglyph.h>
#include <autohint/ahangles.h>
#endif

#include <freetype/ftoutln.h>

#define FACE_GLOBALS(face)  ((AH_Face_Globals*)(face)->autohint.data)

#define AH_USE_IUP


 /*******************************************************************/
 /*******************************************************************/
 /****                                                           ****/
 /****   Hinting routines                                        ****/
 /****                                                           ****/
 /*******************************************************************/
 /*******************************************************************/

  static int  disable_horz_edges = 0;
  static int  disable_vert_edges = 0;

  /* snap a given width in scaled coordinates to one of the */
  /* current standard widths..                              */
  static
  FT_Pos  ah_snap_width( FT_Pos*     widths,
                         FT_Int      count,
                         FT_Pos      width )
  {
    int     n;
    FT_Pos  best      = 64+32+2;
    FT_Pos  reference = width;

    for ( n = 0; n < count; n++ )
    {
      FT_Pos  w;
      FT_Pos  dist;

      w = widths[n];
      dist = width - w;
      if (dist < 0) dist = -dist;
      if (dist < best)
      {
        best      = dist;
        reference = w;
      }
    }

    if ( width >= reference )
    {
      width -= 0x21;
      if ( width < reference )
        width = reference;
    }
    else
    {
      width += 0x21;
      if ( width > reference )
        width = reference;
    }

    return width;
  }



 /* Align one stem edge relative to the previous stem edge */
  static
  void  ah_align_linked_edge( AH_Hinter*  hinter,
                              AH_Edge*    base_edge,
                              AH_Edge*    stem_edge,
                              int         vertical )
  {
    FT_Pos       dist    = stem_edge->opos - base_edge->opos;
    AH_Globals*  globals = &hinter->globals->scaled;
    FT_Pos       sign    = 1;

    if (dist < 0)
    {
      dist = -dist;
      sign = -1;
    }

    if (vertical)
    {
      dist = ah_snap_width( globals->heights, globals->num_heights, dist );

      /* in the case of vertical hinting, always round */
      /* the stem heights to integer pixels..          */
      if (dist >= 64)
        dist = (dist+16) & -64;
      else
        dist = 64;
    }
    else
    {
      dist = ah_snap_width( globals->widths,  globals->num_widths, dist );

      if (hinter->flags & ah_hinter_monochrome)
      {
        /* monochrome horizontal hinting: snap widths to integer pixels */
        /* with a different threshold..                                 */
        if (dist < 64)
          dist = 64;
        else
          dist = (dist+32) & -64;
      }
      else
      {
        /* for horizontal anti-aliased hinting, we adopt a more subtle */
        /* approach, we strengthen small stems, round stems whose size */
        /* is between 1 and 2 pixels to an integer, otherwise nothing  */
        if (dist < 48)
          dist = (dist+64) >> 1;

        else if (dist < 128)
          dist = (dist+42) & -64;

      }
    }
    stem_edge->pos = base_edge->pos + sign*dist;
  }



  static
  void  ah_align_serif_edge( AH_Hinter*  hinter,
                             AH_Edge*    base,
                             AH_Edge*    serif )
  {
    FT_Pos  dist;
    FT_Pos  sign = 1;

    UNUSED(hinter);
    dist = serif->opos - base->opos;
    if (dist < 0)
    {
      dist = -dist;
      sign = -1;
    }

    if (base->flags & ah_edge_done)
    /* do not strengthen serifs */
    {
      if (dist > 64)
        dist = (dist+16) & -64;

      else if (dist <= 32)
        dist = (dist+33) >> 1;
    }
    serif->pos = base->pos + sign*dist;
  }


 /**************************************************************************/
 /**************************************************************************/
 /**************************************************************************/
 /****                                                                  ****/
 /****       E D G E   H I N T I N G                                    ****/
 /****                                                                  ****/
 /****                                                                  ****/
 /**************************************************************************/
 /**************************************************************************/
 /**************************************************************************/

  /* Another alternative edge hinting algorithm */
  static
  void  ah_hint_edges_3( AH_Hinter*  hinter )
  {
    AH_Edge*      edges;
    AH_Edge*      edge_limit;
    AH_Outline*   outline = hinter->glyph;
    FT_Int        dimension;

    edges      = outline->horz_edges;
    edge_limit = edges + outline->num_hedges;

    for ( dimension = 1; dimension >= 0; dimension-- )
    {
      AH_Edge*  edge;
      AH_Edge*  before = 0;
      AH_Edge*  after  = 0;
      AH_Edge*  anchor = 0;
      int       has_serifs = 0;

      if (disable_vert_edges && !dimension)
        goto Next_Dimension;

      if (disable_horz_edges && dimension)
        goto Next_Dimension;

      /* we begin by aligning all stems relative to the blue zone */
      /* if needed.. that's only for horizontal edges..           */
      if (dimension)
      {
        for ( edge = edges; edge < edge_limit; edge++ )
        {
          FT_Pos*  blue;
          AH_Edge *edge1, *edge2;

          if (edge->flags & ah_edge_done)
            continue;

          blue  = edge->blue_edge;
          edge1 = 0;
          edge2 = edge->link;

          if (blue)
          {
            edge1 = edge;
          }
          else if (edge2 && edge2->blue_edge)
          {
            blue  = edge2->blue_edge;
            edge1 = edge2;
            edge2 = edge;
          }

          if (!edge1)
            continue;

          edge1->pos    = blue[0];
          edge1->flags |= ah_edge_done;

          if (edge2 && !edge2->blue_edge)
          {
            ah_align_linked_edge( hinter, edge1, edge2, dimension );
            edge2->flags |= ah_edge_done;
          }

          if (!anchor)
            anchor = edge;
         }
       }

      /* now, we will align all stem edges, trying to maintain the */
      /* relative order of stems in the glyph..                    */
      before = 0;
      after  = 0;
      for ( edge = edges; edge < edge_limit; edge++ )
      {
        AH_Edge  *edge2;

        if (edge->flags & ah_edge_done)
          continue;

        /* skip all non-stem edges */
        edge2 = edge->link;
        if (!edge2)
        {
          has_serifs++;
          continue;
        }

        /* now, align the stem */

        /* this should not happen, but it's better to be safe.. */
        if (edge2->blue_edge || edge2 < edge)
        {
#if 0
          printf( "strange blue alignement, edge %d to %d\n",
                      edge - edges, edge2 - edges );
#endif
          ah_align_linked_edge( hinter, edge2, edge, dimension );
          edge->flags |= ah_edge_done;
          continue;
        }

        {
          FT_Bool  min = 0;
          FT_Pos   delta;

          if (!anchor)
          {
            edge->pos = (edge->opos+32) & -64;
            anchor    = edge;
          }
          else
            edge->pos = anchor->pos + ((edge->opos - anchor->opos + 32) & -64);

          edge->flags |= ah_edge_done;

          if (edge > edges && edge->pos < edge[-1].pos)
          {
            edge->pos = edge[-1].pos;
            min = 1;
          }

          ah_align_linked_edge( hinter, edge, edge2, dimension );
          delta = 0;
          if ( edge2+1 < edge_limit          &&
               edge2[1].flags & ah_edge_done )
            delta = edge2[1].pos - edge2->pos;

          if (delta < 0)
          {
            edge2->pos += delta;
            if (!min)
              edge->pos += delta;
          }
          edge2->flags |= ah_edge_done;
        }
      }

      if (!has_serifs)
        goto Next_Dimension;

      /* now, hint the remaining edges (serifs and single) in order */
      /* to complete our processing..                               */
      for ( edge = edges; edge < edge_limit; edge++ )
      {
        if (edge->flags & ah_edge_done)
          continue;

        if (edge->serif)
        {
          ah_align_serif_edge( hinter, edge->serif, edge );
        }
        else if (!anchor)
        {
          edge->pos = (edge->opos+32) & -64;
          anchor    = edge;
        }
        else
          edge->pos = anchor->pos + ((edge->opos-anchor->opos+32) & -64);

        edge->flags |= ah_edge_done;

        if (edge > edges && edge->pos < edge[-1].pos)
          edge->pos = edge[-1].pos;

        if ( edge+1 < edge_limit && edge[1].flags & ah_edge_done &&
             edge->pos > edge[1].pos)
          edge->pos = edge[1].pos;
      }

    Next_Dimension:
      edges      = outline->vert_edges;
      edge_limit = edges + outline->num_vedges;
    }

  }







  LOCAL_FUNC
  void  ah_hinter_hint_edges( AH_Hinter*  hinter,
                              int         no_horz_edges,
                              int         no_vert_edges )
  {
    disable_horz_edges = no_horz_edges;
    disable_vert_edges = no_vert_edges;

    /* AH_Interpolate_Blue_Edges( hinter ); -- doesn't seem to help   */
    /* reduce the problem of the disappearing eye in the "e" of Times */
    /* also, creates some artifacts near the blue zones ??            */
    {
      ah_hint_edges_3( hinter );

    /* outline optimiser removed temporarily */
#if 0
      if (hinter->flags & ah_hinter_optimize)
      {
        AH_Optimizer  opt;

        if (!AH_Optimizer_Init( &opt, hinter->glyph, hinter->memory ))
        {
          AH_Optimizer_Compute( &opt );
          AH_Optimizer_Done( &opt );
        }
      }
#endif
    }
  }



 /**************************************************************************/
 /**************************************************************************/
 /**************************************************************************/
 /****                                                                  ****/
 /****       P O I N T   H I N T I N G                                  ****/
 /****                                                                  ****/
 /****                                                                  ****/
 /**************************************************************************/
 /**************************************************************************/
 /**************************************************************************/

  static
  void  ah_hinter_align_edge_points( AH_Hinter*  hinter )
  {
    AH_Outline*  outline = hinter->glyph;
    AH_Edge*     edges;
    AH_Edge*     edge_limit;
    FT_Int       dimension;

    edges       = outline->horz_edges;
    edge_limit  = edges + outline->num_hedges;

    for ( dimension = 1; dimension >= 0; dimension-- )
    {
      AH_Edge*   edge;
      AH_Edge*   before;
      AH_Edge*   after;

      before = 0;
      after  = 0;

      edge = edges;
      for ( ; edge < edge_limit; edge++ )
      {
        /* move the points of each segment in each edge to the edge's position */
        AH_Segment*  seg = edge->first;
        do
        {
          AH_Point*  point = seg->first;
          for (;;)
          {
            if (dimension)
            {
              point->y      = edge->pos;
              point->flags |= ah_flah_touch_y;
            }
            else
            {
              point->x      = edge->pos;
              point->flags |= ah_flah_touch_x;
            }
            if (point == seg->last)
              break;

            point = point->next;
          }

          seg = seg->edge_next;
        }
        while (seg != edge->first);
      }
      edges      = outline->vert_edges;
      edge_limit = edges + outline->num_vedges;
    }
  }


  /* hint the strong points - this is equivalent to the TrueType "IP" */
  static
  void  ah_hinter_align_strong_points( AH_Hinter*  hinter )
  {
    AH_Outline*  outline = hinter->glyph;
    FT_Int       dimension;
    AH_Edge*     edges;
    AH_Edge*     edge_limit;
    AH_Point*    points;
    AH_Point*    point_limit;
    AH_Flags     touch_flag;

    points      = outline->points;
    point_limit = points + outline->num_points;

    edges       = outline->horz_edges;
    edge_limit  = edges + outline->num_hedges;
    touch_flag  = ah_flah_touch_y;

    for ( dimension = 1; dimension >= 0; dimension-- )
    {
      AH_Point*  point;
      AH_Edge*   edge;
      AH_Edge*   before;
      AH_Edge*   after;

      before = 0;
      after  = 0;

      if ( edges < edge_limit )
        for ( point = points; point < point_limit; point++ )
        {
          FT_Pos  u, ou, fu;  /* point position */
          FT_Pos  delta;

          if ( point->flags & touch_flag )
            continue;

#ifndef AH_OPTION_NO_WEAK_INTERPOLATION
          /* if this point is candidate to weak interpolation, we'll    */
          /* interpolate it after all strong points have been processed */
          if ( point->flags & ah_flah_weak_interpolation )
            continue;
#endif

          if (dimension) { u = point->fy; ou = point->oy; }
                    else { u = point->fx; ou = point->ox; }

          fu = u;

          /* is the point before the first edge ? */
          edge  = edges;
          delta = edge->fpos - u;
          if (delta >= 0)
          {
            u = edge->pos - (edge->opos - ou);
            goto Store_Point;
          }

          /* is the point after the last edge ? */
          edge  = edge_limit-1;
          delta = u - edge->fpos;
          if ( delta >= 0 )
          {
            u = edge->pos + (ou - edge->opos);
            goto Store_Point;
          }

          /* otherwise, interpolate the point in between */
          {
            AH_Edge*  before = 0;
            AH_Edge*  after  = 0;

            for ( edge = edges; edge < edge_limit; edge++ )
            {
              if ( u == edge->fpos )
              {
                u = edge->pos;
                goto Store_Point;
              }
              if ( u < edge->fpos )
                break;
              before = edge;
            }

            for ( edge = edge_limit-1; edge >= edges; edge-- )
            {
              if ( u == edge->fpos )
              {
                u = edge->pos;
                goto Store_Point;
              }
              if ( u > edge->fpos )
                break;
              after = edge;
            }

            /* assert( before && after && before != after ) */
            u = before->pos + FT_MulDiv( fu - before->fpos,
                                         after->pos - before->pos,
                                         after->fpos - before->fpos );
          }
        Store_Point:

          /* save the point position */
          if (dimension) point->y = u;
                   else  point->x = u;

          point->flags |= touch_flag;
        }

      edges      = outline->vert_edges;
      edge_limit = edges + outline->num_vedges;
      touch_flag = ah_flah_touch_x;
    }
  }


#ifndef AH_OPTION_NO_WEAK_INTERPOLATION
  static
  void  ah_iup_shift( AH_Point*  p1,
                      AH_Point*  p2,
                      AH_Point*  ref )
  {
    AH_Point*  p;
    FT_Pos     delta = ref->u - ref->v;

    for ( p = p1; p < ref; p++ )
      p->u = p->v + delta;

    for ( p = ref+1; p <= p2; p++ )
      p->u = p->v + delta;
  }


  static
  void  ah_iup_interp( AH_Point*  p1,
                       AH_Point*  p2,
                       AH_Point*  ref1,
                       AH_Point*  ref2 )
  {
    AH_Point*  p;
    FT_Pos     u;
    FT_Pos     v1 = ref1->v;
    FT_Pos     v2 = ref2->v;
    FT_Pos     d1 = ref1->u - v1;
    FT_Pos     d2 = ref2->u - v2;

    if (p1 > p2) return;

    if (v1 == v2)
    {
      for ( p = p1; p <= p2; p++ )
      {
        FT_Pos  u = p->v;

        if (u <= v1) u += d1;
                else u += d2;
        p->u = u;
      }
      return;
    }

    if ( v1 < v2 )
    {
      for ( p = p1; p <= p2; p++ )
      {
        u = p->v;

        if (u <= v1)      u += d1;
        else if (u >= v2) u += d2;
        else              u = ref1->u+FT_MulDiv( u-v1, ref2->u-ref1->u, v2-v1 );

        p->u = u;
      }
    }
    else
    {
      for ( p = p1; p <= p2; p++ )
      {
        u = p->v;
        if (u <= v2)      u += d2;
        else if (u >= v1) u += d1;
        else              u = ref1->u+FT_MulDiv( u-v1, ref2->u-ref1->u, v2-v1 );

        p->u = u;
      }
    }
  }

  /* interpolate weak points - this is equivalent to the TrueType "IUP" */
  static
  void  ah_hinter_align_weak_points( AH_Hinter*  hinter )
  {
    AH_Outline*  outline = hinter->glyph;
    FT_Int       dimension;
    AH_Edge*     edges;
    AH_Edge*     edge_limit;
    AH_Point*    points;
    AH_Point*    point_limit;
    AH_Point**   contour_limit;
    AH_Flags     touch_flag;

    points      = outline->points;
    point_limit = points + outline->num_points;

    /* PASS 1 : Move segment points to edge positions */

    edges      = outline->horz_edges;
    edge_limit = edges + outline->num_hedges;
    touch_flag = ah_flah_touch_y;

    contour_limit = outline->contours + outline->num_contours;

    ah_setup_uv( outline, ah_uv_oy );

    for ( dimension = 1; dimension >= 0; dimension-- )
    {
      AH_Point*  point;
      AH_Point*  end_point;
      AH_Point*  first_point;
      AH_Point** contour;

      point   = points;
      contour = outline->contours;

      for ( ; contour < contour_limit; contour++ )
      {
        point       = *contour;
        end_point   = point->prev;
        first_point = point;

        while (point <= end_point && !(point->flags & touch_flag))
          point++;

        if (point <= end_point)
        {
          AH_Point*  first_touched = point;
          AH_Point*  cur_touched   = point;

          point++;
          while ( point <= end_point )
          {
            if (point->flags & touch_flag)
            {
              /* we found two succesive touched points, we interpolate */
              /* all contour points between them..                     */
              ah_iup_interp( cur_touched+1, point-1,
                             cur_touched, point );
              cur_touched = point;
            }
            point++;
          }

          if (cur_touched == first_touched)
          {
            /* this is a special case: only one point was touched in the */
            /* contour.. we thus simply shift the whole contour..        */
            ah_iup_shift( first_point, end_point, cur_touched );
          }
          else
          {
            /* now interpolate after the last touched point to the end */
            /* of the contour..                                        */
            ah_iup_interp( cur_touched+1, end_point,
                           cur_touched, first_touched );

            /* if the first contour point isn't touched, interpolate   */
            /* from the contour start to the first touched point       */
            if (first_touched > points)
              ah_iup_interp( first_point, first_touched-1,
                             cur_touched, first_touched );
          }
        }
      }

      /* now save the interpolated values back to x/y */
      if (dimension)
      {
        for ( point = points; point < point_limit; point++ )
          point->y = point->u;

        touch_flag = ah_flah_touch_x;
        ah_setup_uv( outline, ah_uv_ox );
      }
      else
      {
        for ( point = points; point < point_limit; point++ )
          point->x = point->u;

        break;  /* exit loop */
      }
    }
  }
#endif

  LOCAL_FUNC
  void  ah_hinter_align_points( AH_Hinter*  hinter )
  {
    ah_hinter_align_edge_points( hinter );

#ifndef AH_OPTION_NO_STRONG_INTERPOLATION
    ah_hinter_align_strong_points( hinter );
#endif

#ifndef AH_OPTION_NO_WEAK_INTERPOLATION
    ah_hinter_align_weak_points( hinter );
#endif
  }


 /**************************************************************************/
 /**************************************************************************/
 /**************************************************************************/
 /****                                                                  ****/
 /****       H I N T E R   O B J E C T   M E T H O D S                  ****/
 /****                                                                  ****/
 /****                                                                  ****/
 /**************************************************************************/
 /**************************************************************************/
 /**************************************************************************/

  /* scale and fit the global metrics */
  static
  void  ah_hinter_scale_globals( AH_Hinter*  hinter,
                                 FT_Fixed    x_scale,
                                 FT_Fixed    y_scale )
  {
    FT_Int            n;
    AH_Face_Globals*  globals = hinter->globals;
    AH_Globals*       design = &globals->design;
    AH_Globals*       scaled = &globals->scaled;

    /* copy content */
    *scaled = *design;

    /* scale the standard widths & heights */
    for ( n = 0; n < design->num_widths; n++ )
      scaled->widths[n] = FT_MulFix( design->widths[n], x_scale );

    for ( n = 0; n < design->num_heights; n++ )
      scaled->heights[n] = FT_MulFix( design->heights[n], y_scale );

    /* scale the blue zones */
    for ( n = 0; n < ah_blue_max; n++ )
    {
      FT_Pos  delta, delta2;

      delta = design->blue_shoots[n] - design->blue_refs[n];
      delta2 = delta; if (delta < 0) delta2 = -delta2;
      delta2 = FT_MulFix( delta2, y_scale );

      if (delta2 < 32)
        delta2 = 0;
      else if (delta2 < 64)
        delta2 = 32 + (((delta2-32)+16) & -32);
      else
        delta2 = (delta2+32) & -64;

      if (delta < 0) delta2 = -delta2;

      scaled->blue_refs  [n] = (FT_MulFix(design->blue_refs[n],y_scale)+32) & -64;
      scaled->blue_shoots[n] = scaled->blue_refs[n] + delta2;
    }
    globals->x_scale = x_scale;
    globals->y_scale = y_scale;
  }


  static
  void  ah_hinter_align( AH_Hinter*  hinter )
  {
    ah_hinter_align_edge_points( hinter );
    ah_hinter_align_points( hinter );
  }


 /* finalise a hinter object */
  void ah_hinter_done( AH_Hinter*  hinter )
  {
    if (hinter)
    {
      FT_Memory  memory = hinter->memory;

      ah_loader_done( hinter->loader );
      ah_outline_done( hinter->glyph );

     /* note: the globals pointer is _not_ owned by the hinter */
     /*       but by the current face object, we don't need to */
     /*       release it..                                     */
      hinter->globals = 0;
      hinter->face    = 0;

      FREE(hinter);
    }
  }


 /* create a new empty hinter object */
  FT_Error ah_hinter_new( FT_Library library, AH_Hinter*  *ahinter )
  {
    AH_Hinter*  hinter = 0;
    FT_Memory   memory = library->memory;
    FT_Error    error;

    *ahinter = 0;

    /* allocate object */
    if (ALLOC( hinter, sizeof(*hinter) )) goto Exit;

    hinter->memory = memory;
    hinter->flags  = 0;

    /* allocate outline and loader */
    error = ah_outline_new( memory, &hinter->glyph )  ||
            ah_loader_new ( memory, &hinter->loader ) ||
            ah_loader_create_extra( hinter->loader );
    if (error) goto Exit;

    *ahinter = hinter;

  Exit:
    if (error)
      ah_hinter_done( hinter );

    return error;
  }


 /* create a face's autohint globals */
  FT_Error  ah_hinter_new_face_globals( AH_Hinter*  hinter,
                                        FT_Face     face,
                                        AH_Globals* globals )
  {
    FT_Error          error;
    FT_Memory         memory = hinter->memory;
    AH_Face_Globals*  face_globals;

    if ( ALLOC( face_globals, sizeof(*face_globals) ) )
      goto Exit;

    hinter->face    = face;
    hinter->globals = face_globals;
    if (globals)
      face_globals->design = *globals;
    else
      ah_hinter_compute_globals( hinter );

    face->autohint.data      = face_globals;
    face->autohint.finalizer = (FT_Generic_Finalizer)ah_hinter_done_face_globals;
    face_globals->face       = face;

  Exit:
    return error;
  }



 /* discard a face's autohint globals */
  void  ah_hinter_done_face_globals( AH_Face_Globals*  globals )
  {
    FT_Face   face   = globals->face;
    FT_Memory memory = face->memory;

    FREE( globals );
  }



 static
 FT_Error  ah_hinter_load( AH_Hinter*  hinter,
                           FT_UInt     glyph_index,
                           FT_UInt     load_flags,
                           FT_UInt     depth )
 {
   FT_Face           face    = hinter->face;
   FT_GlyphSlot      slot    = face->glyph;
   FT_Fixed          x_scale = face->size->metrics.x_scale;
   FT_Fixed          y_scale = face->size->metrics.y_scale;
   FT_Glyph_Metrics  metrics;  /* temporary metrics */
   FT_Error          error;
   AH_Outline*       outline = hinter->glyph;
   AH_Loader*        gloader = hinter->loader;
   FT_Bool           no_horz_hints = (load_flags & AH_HINT_NO_HORZ_EDGES) != 0;
   FT_Bool           no_vert_hints = (load_flags & AH_HINT_NO_VERT_EDGES) != 0;

   /* load the glyph */
   error = FT_Load_Glyph( face, glyph_index, load_flags );
   if (error) goto Exit;

   /* save current glyph metrics */
   metrics = slot->metrics;

   switch (slot->format)
   {
     case ft_glyph_format_outline:
       {
         /* first of all, copy the outline points in the loader's current  */
         /* extra points, which is used to keep original glyph coordinates */
         error = ah_loader_check_points( gloader, slot->outline.n_points+2,
                                         slot->outline.n_contours );
         if (error) goto Exit;

         MEM_Copy( gloader->current.extra_points, slot->outline.points,
                   slot->outline.n_points * sizeof(FT_Vector) );

         MEM_Copy( gloader->current.outline.contours, slot->outline.contours,
                   slot->outline.n_contours*sizeof(short) );

         MEM_Copy( gloader->current.outline.tags, slot->outline.tags,
                   slot->outline.n_points * sizeof(char) );

         gloader->current.outline.n_points   = slot->outline.n_points;
         gloader->current.outline.n_contours = slot->outline.n_contours;

         /* compute original phantom points */
         hinter->pp1.x = 0;
         hinter->pp1.y = 0;
         hinter->pp2.x = FT_MulFix( slot->metrics.horiAdvance, x_scale );
         hinter->pp2.y = 0;

         /* be sure to check for spacing glyphs */
         if (slot->outline.n_points == 0)
           goto Hint_Metrics;

         /* now, load the slot image into the auto-outline, and run the */
         /* automatic hinting process..                                 */
         error = ah_outline_load( outline, face );   /* XXXX: change to slot */
         if (error) goto Exit;

         /* perform feature detection */
         ah_outline_detect_features( outline );

         if ( !no_horz_hints )
         {
           ah_outline_compute_blue_edges( outline, hinter->globals );
           ah_outline_scale_blue_edges( outline, hinter->globals );
         }

         /* perform alignment control */
         ah_hinter_hint_edges( hinter, no_horz_hints, no_vert_hints );
         ah_hinter_align( hinter );

         /* now save the current outline into the loader's current table */
         ah_outline_save( outline, gloader );

         /* we now need to hint the metrics according to the change in */
         /* width/positioning that occured during the hinting process  */
         {
           FT_Pos  old_width, new_width;
           FT_Pos  old_advance, new_advance;
           FT_Pos  old_lsb, new_lsb;
           AH_Edge*  edge1   = outline->vert_edges;            /* left-most edge  */
           AH_Edge*  edge2   = edge1 + outline->num_vedges-1;  /* right-mode edge */

           old_width = edge2->opos - edge1->opos;
           new_width = edge2->pos  - edge1->pos;

           old_advance = hinter->pp2.x;
           old_lsb     = edge1->opos;
           new_lsb     = edge1->pos;

           new_advance = old_advance + (new_width+new_lsb-old_width-old_lsb);

           hinter->pp1.x = ((new_lsb - old_lsb)+32) & -64;
           hinter->pp2.x = ((edge2->pos + (old_advance - edge2->opos))+32) & -64;
         }

         /* good, we simply add the glyph to our loader's base */
         ah_loader_add( gloader );
       }
       break;

     case ft_glyph_format_composite:
       {
         FT_UInt       nn, num_subglyphs = slot->num_subglyphs;
         FT_UInt       num_base_subgs, start_point, start_contour;
         FT_SubGlyph*  subglyph;

         start_point   = gloader->base.outline.n_points;
         start_contour = gloader->base.outline.n_contours;

         /* first of all, copy the subglyph descriptors in the glyph loader */
         error = ah_loader_check_subglyphs( gloader, num_subglyphs );
         if (error) goto Exit;

         MEM_Copy( gloader->current.subglyphs, slot->subglyphs,
                   num_subglyphs*sizeof(FT_SubGlyph) );

         gloader->current.num_subglyphs = num_subglyphs;
         num_base_subgs = gloader->base.num_subglyphs;

         /* now, read each subglyph independently */
         for ( nn = 0; nn < num_subglyphs; nn++ )
         {
           FT_Vector  pp1, pp2;
           FT_Pos     x, y;
           FT_UInt    num_points, num_new_points, num_base_points;

           /* gloader.current.subglyphs can change during glyph loading due */
           /* to re-allocation. We must recompute the current subglyph on   */
           /* each iteration..                                              */
           subglyph = gloader->base.subglyphs + num_base_subgs + nn;

           pp1 = hinter->pp1;
           pp2 = hinter->pp2;

           num_base_points = gloader->base.outline.n_points;

           error = ah_hinter_load( hinter, subglyph->index, load_flags, depth+1 );
           if ( error ) goto Exit;

           /* recompute subglyph pointer */
           subglyph = gloader->base.subglyphs + num_base_subgs + nn;

           if ( subglyph->flags & FT_SUBGLYPH_FLAG_USE_MY_METRICS )
           {
             pp1 = hinter->pp1;
             pp2 = hinter->pp2;
           }
           else
           {
             hinter->pp1 = pp1;
             hinter->pp2 = pp2;
           }

           num_points     = gloader->base.outline.n_points;
           num_new_points = num_points - num_base_points;

           /* now perform the transform required for this subglyph */

           if ( subglyph->flags & ( FT_SUBGLYPH_FLAG_SCALE    |
                                    FT_SUBGLYPH_FLAG_XY_SCALE |
                                    FT_SUBGLYPH_FLAG_2X2      ) )
           {
             FT_Vector*  cur   = gloader->base.outline.points + num_base_points;
             FT_Vector*  org   = gloader->base.extra_points   + num_base_points;
             FT_Vector*  limit = cur + num_new_points;

             for ( ; cur < limit; cur++, org++ )
             {
               FT_Vector_Transform( cur, &subglyph->transform );
               FT_Vector_Transform( org, &subglyph->transform );
             }
           }

           /* apply offset */

           if ( !( subglyph->flags & FT_SUBGLYPH_FLAG_ARGS_ARE_XY_VALUES ) )
           {
             FT_Int      k = subglyph->arg1;
             FT_UInt     l = subglyph->arg2;
             FT_Vector*  p1;
             FT_Vector*  p2;

             if ( start_point + k >= num_base_points          ||
                                l >= (FT_UInt)num_new_points  )
             {
               error = FT_Err_Invalid_Composite;
               goto Exit;
             }

             l += num_base_points;

             /* for now, only use the current point coordinates     */
             /* we may consider another approach in the near future */
             p1 = gloader->base.outline.points + start_point + k;
             p2 = gloader->base.outline.points + start_point + l;

             x = p1->x - p2->x;
             y = p1->y - p2->y;
           }
           else
           {
             x = FT_MulFix( subglyph->arg1, x_scale );
             y = FT_MulFix( subglyph->arg2, y_scale );

             x = ( x + 32 ) & -64;
             y = ( y + 32 ) & -64;
           }

           {
             FT_Outline  dummy = gloader->base.outline;
             dummy.points  += num_base_points;
             dummy.n_points = num_new_points;

             FT_Outline_Translate( &dummy, x, y );
           }
         }
       }
       break;

     default:
       /* we don't support other formats (yet ?) */
       error = FT_Err_Unimplemented_Feature;
   }

 Hint_Metrics:
   if (depth == 0)
   {
     FT_BBox  bbox;

     /* we must translate our final outline by -pp1.x, and compute */
     /* the new metrics..                                          */
     if (hinter->pp1.x)
       FT_Outline_Translate( &gloader->base.outline, -hinter->pp1.x, 0 );

     FT_Outline_Get_CBox( &gloader->base.outline, &bbox );
     bbox.xMin &= -64;
     bbox.yMin &= -64;
     bbox.xMax  = (bbox.xMax+63) & -64;
     bbox.yMax  = (bbox.yMax+63) & -64;

     slot->metrics.width        = (bbox.xMax - bbox.xMin);
     slot->metrics.height       = (bbox.yMax - bbox.yMin);
     slot->metrics.horiBearingX = bbox.xMin;
     slot->metrics.horiBearingY = bbox.yMax;
     slot->metrics.horiAdvance  = hinter->pp2.x - hinter->pp1.x;
     /* XXXX: TO DO - slot->linearHoriAdvance */

     /* now copy outline into glyph slot */
     ah_loader_rewind( slot->loader );
     error = ah_loader_copy_points( slot->loader, gloader );
     if (error) goto Exit;

     slot->outline = slot->loader->base.outline;
     slot->format  = ft_glyph_format_outline;
   }

 Exit:
   return error;
 }


 /* load and hint a given glyph */
 FT_Error  ah_hinter_load_glyph( AH_Hinter*    hinter,
                                 FT_GlyphSlot  slot,
                                 FT_Size       size,
                                 FT_UInt       glyph_index,
                                 FT_Int        load_flags )
 {
   FT_Face           face         = slot->face;
   FT_Error          error;
   FT_Fixed          x_scale      = size->metrics.x_scale;
   FT_Fixed          y_scale      = size->metrics.y_scale;
   AH_Face_Globals*  face_globals = FACE_GLOBALS(face);

   /* first of all, we need to check that we're using the correct face and */
   /* global hints to load the glyph                                       */
   if ( hinter->face != face || hinter->globals != face_globals )
   {
     hinter->face = face;
     if (!face_globals)
     {
       error = ah_hinter_new_face_globals( hinter, face, 0 );
       if (error) goto Exit;
     }
     hinter->globals = FACE_GLOBALS(face);
     face_globals    = FACE_GLOBALS(face);
   }

   /* now, we must check the current character pixel size to see if we need */
   /* to rescale the global metrics..                                       */
   if ( face_globals->x_scale != x_scale ||
        face_globals->y_scale != y_scale )
   {
     ah_hinter_scale_globals( hinter, x_scale, y_scale );
   }

   load_flags |= FT_LOAD_NO_SCALE | FT_LOAD_NO_RECURSE;

   ah_loader_rewind( hinter->loader );

   error = ah_hinter_load( hinter, glyph_index, load_flags, 0 );

 Exit:
   return error;
 }


 /* retrieve a face's autohint globals for client applications */
  void  ah_hinter_get_global_hints( AH_Hinter*  hinter,
                                    FT_Face     face,
                                    void*      *global_hints,
                                    long       *global_len )
  {
    AH_Globals*  globals = 0;
    FT_Memory    memory  = hinter->memory;
    FT_Error     error;

    /* allocate new master globals */
    if (ALLOC( globals, sizeof(*globals)))
      goto Fail;

    /* compute face globals if needed */
    if (!FACE_GLOBALS(face))
    {
      error = ah_hinter_new_face_globals( hinter, face, 0 );
      if (error) goto Fail;
    }

    *globals = FACE_GLOBALS(face)->design;
    *global_hints = globals;
    *global_len   = sizeof(*globals);
    return;

  Fail:
    FREE( globals );
    *global_hints = 0;
    *global_len   = 0;
  }


  void  ah_hinter_done_global_hints( AH_Hinter*  hinter,
                                     void*       global_hints )
  {
    FT_Memory memory = hinter->memory;
    FREE( global_hints );
  }