text:
/****************************************************************************
 *
 * afmodule.c
 *
 *   Auto-fitter module implementation (body).
 *
 * Copyright (C) 2003-2020 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

#include "afglobal.h"
#include "afmodule.h"
#include "afloader.h"
#include "aferrors.h"

#ifdef FT_DEBUG_AUTOFIT

#ifndef FT_MAKE_OPTION_SINGLE_OBJECT

#ifdef __cplusplus
  extern "C" {
#endif
  extern void
  af_glyph_hints_dump_segments( AF_GlyphHints  hints,
                                FT_Bool        to_stdout );
  extern void
  af_glyph_hints_dump_points( AF_GlyphHints  hints,
                              FT_Bool        to_stdout );
  extern void
  af_glyph_hints_dump_edges( AF_GlyphHints  hints,
                             FT_Bool        to_stdout );
#ifdef __cplusplus
  }
#endif

#endif

  int  _af_debug_disable_horz_hints;
  int  _af_debug_disable_vert_hints;
  int  _af_debug_disable_blue_hints;

  /* we use a global object instead of a local one for debugging */
  AF_GlyphHintsRec  _af_debug_hints_rec[1];

  void*  _af_debug_hints = _af_debug_hints_rec;
#endif

#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H
#include FT_DRIVER_H
#include FT_SERVICE_PROPERTIES_H

  /**************************************************************************
   *
   * The macro FT_COMPONENT is used in trace mode.  It is an implicit
   * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
   * messages during execution.
   */
#undef  FT_COMPONENT
#define FT_COMPONENT  afmodule

  static FT_Error
  af_property_get_face_globals( FT_Face          face,
                                AF_FaceGlobals*  aglobals,
                                AF_Module        module )
  {
    FT_Error        error = FT_Err_Ok;
    AF_FaceGlobals  globals;

    if ( !face )
      return FT_THROW( Invalid_Face_Handle );

    globals = (AF_FaceGlobals)face->autohint.data;
    if ( !globals )
    {
      /* trigger computation of the global style data */
      /* in case it hasn't been done yet              */
      error = af_face_globals_new( face, &globals, module );
      if ( !error )
      {
        face->autohint.data =
          (FT_Pointer)globals;
        face->autohint.finalizer =
          (FT_Generic_Finalizer)af_face_globals_free;
      }
    }

    if ( !error )
      *aglobals = globals;

    return error;
  }

  static FT_Error
  af_property_set( FT_Module    ft_module,
                   const char*  property_name,
                   const void*  value,
                   FT_Bool      value_is_string )
  {
    FT_Error   error  = FT_Err_Ok;
    AF_Module  module = (AF_Module)ft_module;

#ifndef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
    FT_UNUSED( value_is_string );
#endif

    if ( !ft_strcmp( property_name, "fallback-script" ) )
    {
      FT_UInt*  fallback_script;
      FT_UInt   ss;

#ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
      if ( value_is_string )
        return FT_THROW( Invalid_Argument );
#endif

      fallback_script = (FT_UInt*)value;

      /* We translate the fallback script to a fallback style that uses */
      /* `fallback-script' as its script and `AF_COVERAGE_NONE' as its  */
      /* coverage value.                                                */
      for ( ss = 0; af_style_classes[ss]; ss++ )
      {
        AF_StyleClass  style_class = af_style_classes[ss];

        if ( (FT_UInt)style_class->script == *fallback_script &&
             style_class->coverage == AF_COVERAGE_DEFAULT     )
        {
          module->fallback_style = ss;
          break;
        }
      }

      if ( !af_style_classes[ss] )
      {
        FT_TRACE0(( "af_property_set: Invalid value %d for property `%s'\n",
                    fallback_script, property_name ));
        return FT_THROW( Invalid_Argument );
      }

      return error;
    }
    else if ( !ft_strcmp( property_name, "default-script" ) )
    {
      FT_UInt*  default_script;

#ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
      if ( value_is_string )
        return FT_THROW( Invalid_Argument );
#endif

      default_script = (FT_UInt*)value;

      module->default_script = *default_script;

      return error;
    }
    else if ( !ft_strcmp( property_name, "increase-x-height" ) )
    {
      FT_Prop_IncreaseXHeight*  prop;
      AF_FaceGlobals            globals;

#ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
      if ( value_is_string )
        return FT_THROW( Invalid_Argument );
#endif

      prop = (FT_Prop_IncreaseXHeight*)value;

      error = af_property_get_face_globals( prop->face, &globals, module );
      if ( !error )
        globals->increase_x_height = prop->limit;

      return error;
    }
#ifdef AF_CONFIG_OPTION_USE_WARPER
    else if ( !ft_strcmp( property_name, "warping" ) )
    {
#ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
      if ( value_is_string )
      {
        const char*  s = (const char*)value;
        long         w = ft_strtol( s, NULL, 10 );

        if ( w == 0 )
          module->warping = 0;
        else if ( w == 1 )
          module->warping = 1;
        else
          return FT_THROW( Invalid_Argument );
      }
      else
#endif
      {
        FT_Bool*  warping = (FT_Bool*)value;

        module->warping = *warping;
      }

      return error;
    }
#endif /* AF_CONFIG_OPTION_USE_WARPER */
    else if ( !ft_strcmp( property_name, "darkening-parameters" ) )
    {
      FT_Int*  darken_params;
      FT_Int   x1, y1, x2, y2, x3, y3, x4, y4;

#ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
      FT_Int   dp[8];

      if ( value_is_string )
      {
        const char*  s = (const char*)value;
        char*        ep;
        int          i;

        /* eight comma-separated numbers */
        for ( i = 0; i < 7; i++ )
        {
          dp[i] = (FT_Int)ft_strtol( s, &ep, 10 );
          if ( *ep != ',' || s == ep )
            return FT_THROW( Invalid_Argument );

          s = ep + 1;
        }

        dp[7] = (FT_Int)ft_strtol( s, &ep, 10 );
        if ( !( *ep == '\0' || *ep == ' ' ) || s == ep )
          return FT_THROW( Invalid_Argument );

        darken_params = dp;
      }
      else
#endif
        darken_params = (FT_Int*)value;

      x1 = darken_params[0];
      y1 = darken_params[1];
      x2 = darken_params[2];
      y2 = darken_params[3];
      x3 = darken_params[4];
      y3 = darken_params[5];
      x4 = darken_params[6];
      y4 = darken_params[7];

      if ( x1 < 0   || x2 < 0   || x3 < 0   || x4 < 0   ||
           y1 < 0   || y2 < 0   || y3 < 0   || y4 < 0   ||
           x1 > x2  || x2 > x3  || x3 > x4              ||
           y1 > 500 || y2 > 500 || y3 > 500 || y4 > 500 )
        return FT_THROW( Invalid_Argument );

      module->darken_params[0] = x1;
      module->darken_params[1] = y1;
      module->darken_params[2] = x2;
      module->darken_params[3] = y2;
      module->darken_params[4] = x3;
      module->darken_params[5] = y3;
      module->darken_params[6] = x4;
      module->darken_params[7] = y4;

      return error;
    }
    else if ( !ft_strcmp( property_name, "no-stem-darkening" ) )
    {
#ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
      if ( value_is_string )
      {
        const char*  s   = (const char*)value;
        long         nsd = ft_strtol( s, NULL, 10 );

        if ( !nsd )
          module->no_stem_darkening = FALSE;
        else
          module->no_stem_darkening = TRUE;
      }
      else
#endif
      {
        FT_Bool*  no_stem_darkening = (FT_Bool*)value;

        module->no_stem_darkening = *no_stem_darkening;
      }

      return error;
    }

    FT_TRACE0(( "af_property_set: missing property `%s'\n",
                property_name ));
    return FT_THROW( Missing_Property );
  }

  static FT_Error
  af_property_get( FT_Module    ft_module,
                   const char*  property_name,
                   void*        value )
  {
    FT_Error   error          = FT_Err_Ok;
    AF_Module  module         = (AF_Module)ft_module;
    FT_UInt    fallback_style = module->fallback_style;
    FT_UInt    default_script = module->default_script;
#ifdef AF_CONFIG_OPTION_USE_WARPER
    FT_Bool    warping        = module->warping;
#endif

    if ( !ft_strcmp( property_name, "glyph-to-script-map" ) )
    {
      FT_Prop_GlyphToScriptMap*  prop = (FT_Prop_GlyphToScriptMap*)value;
      AF_FaceGlobals             globals;

      error = af_property_get_face_globals( prop->face, &globals, module );
      if ( !error )
        prop->map = globals->glyph_styles;

      return error;
    }
    else if ( !ft_strcmp( property_name, "fallback-script" ) )
    {
      FT_UInt*  val = (FT_UInt*)value;

      AF_StyleClass  style_class = af_style_classes[fallback_style];

      *val = style_class->script;

      return error;
    }
    else if ( !ft_strcmp( property_name, "default-script" ) )
    {
      FT_UInt*  val = (FT_UInt*)value;

      *val = default_script;

      return error;
    }
    else if ( !ft_strcmp( property_name, "increase-x-height" ) )
    {
      FT_Prop_IncreaseXHeight*  prop = (FT_Prop_IncreaseXHeight*)value;
      AF_FaceGlobals            globals;

      error = af_property_get_face_globals( prop->face, &globals, module );
      if ( !error )
        prop->limit = globals->increase_x_height;

      return error;
    }
#ifdef AF_CONFIG_OPTION_USE_WARPER
    else if ( !ft_strcmp( property_name, "warping" ) )
    {
      FT_Bool*  val = (FT_Bool*)value;

      *val = warping;

      return error;
    }
#endif /* AF_CONFIG_OPTION_USE_WARPER */
    else if ( !ft_strcmp( property_name, "darkening-parameters" ) )
    {
      FT_Int*  darken_params = module->darken_params;
      FT_Int*  val           = (FT_Int*)value;

      val[0] = darken_params[0];
      val[1] = darken_params[1];
      val[2] = darken_params[2];
      val[3] = darken_params[3];
      val[4] = darken_params[4];
      val[5] = darken_params[5];
      val[6] = darken_params[6];
      val[7] = darken_params[7];

      return error;
    }
    else if ( !ft_strcmp( property_name, "no-stem-darkening" ) )
    {
      FT_Bool   no_stem_darkening = module->no_stem_darkening;
      FT_Bool*  val               = (FT_Bool*)value;

      *val = no_stem_darkening;

      return error;
    }

    FT_TRACE0(( "af_property_get: missing property `%s'\n",
                property_name ));
    return FT_THROW( Missing_Property );
  }

  FT_DEFINE_SERVICE_PROPERTIESREC(
    af_service_properties,

    (FT_Properties_SetFunc)af_property_set,        /* set_property */
    (FT_Properties_GetFunc)af_property_get )       /* get_property */

  FT_DEFINE_SERVICEDESCREC1(
    af_services,

    FT_SERVICE_ID_PROPERTIES, &af_service_properties )

  FT_CALLBACK_DEF( FT_Module_Interface )
  af_get_interface( FT_Module    module,
                    const char*  module_interface )
  {
    FT_UNUSED( module );

    return ft_service_list_lookup( af_services, module_interface );
  }

  FT_CALLBACK_DEF( FT_Error )
  af_autofitter_init( FT_Module  ft_module )      /* AF_Module */
  {
    AF_Module  module = (AF_Module)ft_module;

    module->fallback_style    = AF_STYLE_FALLBACK;
    module->default_script    = AF_SCRIPT_DEFAULT;
#ifdef AF_CONFIG_OPTION_USE_WARPER
    module->warping           = 0;
#endif
    module->no_stem_darkening = TRUE;

    module->darken_params[0]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1;
    module->darken_params[1]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1;
    module->darken_params[2]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2;
    module->darken_params[3]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2;
    module->darken_params[4]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3;
    module->darken_params[5]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3;
    module->darken_params[6]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4;
    module->darken_params[7]  = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4;

    return FT_Err_Ok;
  }

  FT_CALLBACK_DEF( void )
  af_autofitter_done( FT_Module  ft_module )      /* AF_Module */
  {
    FT_UNUSED( ft_module );

#ifdef FT_DEBUG_AUTOFIT
    if ( _af_debug_hints_rec->memory )
      af_glyph_hints_done( _af_debug_hints_rec );
#endif
  }

  FT_CALLBACK_DEF( FT_Error )
  af_autofitter_load_glyph( AF_Module     module,
                            FT_GlyphSlot  slot,
                            FT_Size       size,
                            FT_UInt       glyph_index,
                            FT_Int32      load_flags )
  {
    FT_Error   error  = FT_Err_Ok;
    FT_Memory  memory = module->root.library->memory;

#ifdef FT_DEBUG_AUTOFIT

    /* in debug mode, we use a global object that survives this routine */

    AF_GlyphHints  hints = _af_debug_hints_rec;
    AF_LoaderRec   loader[1];

    FT_UNUSED( size );

    if ( hints->memory )
      af_glyph_hints_done( hints );

    af_glyph_hints_init( hints, memory );
    af_loader_init( loader, hints );

    error = af_loader_load_glyph( loader, module, slot->face,
                                  glyph_index, load_flags );

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( ft_trace_levels[FT_TRACE_COMP( FT_COMPONENT )] )
    {
#endif
      af_glyph_hints_dump_points( hints, 0 );
      af_glyph_hints_dump_segments( hints, 0 );
      af_glyph_hints_dump_edges( hints, 0 );
#ifdef FT_DEBUG_LEVEL_TRACE
    }
#endif

    af_loader_done( loader );

    return error;

#else /* !FT_DEBUG_AUTOFIT */

    AF_GlyphHintsRec  hints[1];
    AF_LoaderRec      loader[1];

    FT_UNUSED( size );

    af_glyph_hints_init( hints, memory );
    af_loader_init( loader, hints );

    error = af_loader_load_glyph( loader, module, slot->face,
                                  glyph_index, load_flags );

    af_loader_done( loader );
    af_glyph_hints_done( hints );

    return error;

#endif /* !FT_DEBUG_AUTOFIT */
  }

  FT_DEFINE_AUTOHINTER_INTERFACE(
    af_autofitter_interface,

    NULL,                                                    /* reset_face */
    NULL,                                              /* get_global_hints */
    NULL,                                             /* done_global_hints */
    (FT_AutoHinter_GlyphLoadFunc)af_autofitter_load_glyph )  /* load_glyph */

  FT_DEFINE_MODULE(
    autofit_module_class,

    FT_MODULE_HINTER,
    sizeof ( AF_ModuleRec ),

    "autofitter",
    0x10000L,   /* version 1.0 of the autofitter  */
    0x20000L,   /* requires FreeType 2.0 or above */

    (const void*)&af_autofitter_interface,

    (FT_Module_Constructor)af_autofitter_init,  /* module_init   */
    (FT_Module_Destructor) af_autofitter_done,  /* module_done   */
    (FT_Module_Requester)  af_get_interface     /* get_interface */
  )

/* END */


text:
/****************************************************************************
 *
 * afdummy.c
 *
 *   Auto-fitter dummy routines to be used if no hinting should be
 *   performed (body).
 *
 * Copyright (C) 2003-2020 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

#include "afdummy.h"
#include "afhints.h"
#include "aferrors.h"

  static FT_Error
  af_dummy_hints_init( AF_GlyphHints    hints,
                       AF_StyleMetrics  metrics )
  {
    af_glyph_hints_rescale( hints, metrics );

    hints->x_scale = metrics->scaler.x_scale;
    hints->y_scale = metrics->scaler.y_scale;
    hints->x_delta = metrics->scaler.x_delta;
    hints->y_delta = metrics->scaler.y_delta;

    return FT_Err_Ok;
  }

  static FT_Error
  af_dummy_hints_apply( FT_UInt          glyph_index,
                        AF_GlyphHints    hints,
                        FT_Outline*      outline,
                        AF_StyleMetrics  metrics )
  {
    FT_Error  error;

    FT_UNUSED( glyph_index );
    FT_UNUSED( metrics );

    error = af_glyph_hints_reload( hints, outline );
    if ( !error )
      af_glyph_hints_save( hints, outline );

    return error;
  }

  AF_DEFINE_WRITING_SYSTEM_CLASS(
    af_dummy_writing_system_class,

    AF_WRITING_SYSTEM_DUMMY,

    sizeof ( AF_StyleMetricsRec ),

    (AF_WritingSystem_InitMetricsFunc) NULL,                /* style_metrics_init    */
    (AF_WritingSystem_ScaleMetricsFunc)NULL,                /* style_metrics_scale   */
    (AF_WritingSystem_DoneMetricsFunc) NULL,                /* style_metrics_done    */
    (AF_WritingSystem_GetStdWidthsFunc)NULL,                /* style_metrics_getstdw */

    (AF_WritingSystem_InitHintsFunc)   af_dummy_hints_init, /* style_hints_init      */
    (AF_WritingSystem_ApplyHintsFunc)  af_dummy_hints_apply /* style_hints_apply     */
  )

/* END */

repo_language: C

mime_type: text/x-c


text:
/****************************************************************************
 *
 * autofit.c
 *
 *   Auto-fitter module (body).
 *
 * Copyright (C) 2003-2020 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

#define FT_MAKE_OPTION_SINGLE_OBJECT
#include <ft2build.h>

#include "afangles.c"
#include "afblue.c"
#include "afcjk.c"
#include "afdummy.c"
#include "afglobal.c"
#include "afhints.c"
#include "afindic.c"
#include "aflatin.c"
#include "aflatin2.c"
#include "afloader.c"
#include "afmodule.c"
#include "afranges.c"
#include "afshaper.c"
#include "afwarp.c"

/* END */


text:
/****************************************************************************
 *
 * afhints.c
 *
 *   Auto-fitter hinting routines (body).
 *
 * Copyright (C) 2003-2020 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

#include "afhints.h"
#include "aferrors.h"
#include FT_INTERNAL_CALC_H
#include FT_INTERNAL_DEBUG_H

  /**************************************************************************
   *
   * The macro FT_COMPONENT is used in trace mode.  It is an implicit
   * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
   * messages during execution.
   */
#undef  FT_COMPONENT
#define FT_COMPONENT  afhints

  /* Get new segment for given axis. */

  FT_LOCAL_DEF( FT_Error )
  af_axis_hints_new_segment( AF_AxisHints  axis,
                             FT_Memory     memory,
                             AF_Segment   *asegment )
  {
    FT_Error    error   = FT_Err_Ok;
    AF_Segment  segment = NULL;

    if ( axis->num_segments < AF_SEGMENTS_EMBEDDED )
    {
      if ( !axis->segments )
      {
        axis->segments     = axis->embedded.segments;
        axis->max_segments = AF_SEGMENTS_EMBEDDED;
      }
    }
    else if ( axis->num_segments >= axis->max_segments )
    {
      FT_Int  old_max = axis->max_segments;
      FT_Int  new_max = old_max;
      FT_Int  big_max = (FT_Int)( FT_INT_MAX / sizeof ( *segment ) );

      if ( old_max >= big_max )
      {
        error = FT_THROW( Out_Of_Memory );
        goto Exit;
      }

      new_max += ( new_max >> 2 ) + 4;
      if ( new_max < old_max || new_max > big_max )
        new_max = big_max;

      if ( axis->segments == axis->embedded.segments )
      {
        if ( FT_NEW_ARRAY( axis->segments, new_max ) )
          goto Exit;
        ft_memcpy( axis->segments, axis->embedded.segments,
                   sizeof ( axis->embedded.segments ) );
      }
      else
      {
        if ( FT_RENEW_ARRAY( axis->segments, old_max, new_max ) )
          goto Exit;
      }

      axis->max_segments = new_max;
    }

    segment = axis->segments + axis->num_segments++;

  Exit:
    *asegment = segment;
    return error;
  }

  /* Get new edge for given axis, direction, and position, */
  /* without initializing the edge itself.                 */

  FT_LOCAL( FT_Error )
  af_axis_hints_new_edge( AF_AxisHints  axis,
                          FT_Int        fpos,
                          AF_Direction  dir,
                          FT_Bool       top_to_bottom_hinting,
                          FT_Memory     memory,
                          AF_Edge      *anedge )
  {
    FT_Error  error = FT_Err_Ok;
    AF_Edge   edge  = NULL;
    AF_Edge   edges;

    if ( axis->num_edges < AF_EDGES_EMBEDDED )
    {
      if ( !axis->edges )
      {
        axis->edges     = axis->embedded.edges;
        axis->max_edges = AF_EDGES_EMBEDDED;
      }
    }
    else if ( axis->num_edges >= axis->max_edges )
    {
      FT_Int  old_max = axis->max_edges;
      FT_Int  new_max = old_max;
      FT_Int  big_max = (FT_Int)( FT_INT_MAX / sizeof ( *edge ) );

      if ( old_max >= big_max )
      {
        error = FT_THROW( Out_Of_Memory );
        goto Exit;
      }

      new_max += ( new_max >> 2 ) + 4;
      if ( new_max < old_max || new_max > big_max )
        new_max = big_max;

      if ( axis->edges == axis->embedded.edges )
      {
        if ( FT_NEW_ARRAY( axis->edges, new_max ) )
          goto Exit;
        ft_memcpy( axis->edges, axis->embedded.edges,
                   sizeof ( axis->embedded.edges ) );
      }
      else
      {
        if ( FT_RENEW_ARRAY( axis->edges, old_max, new_max ) )
          goto Exit;
      }

      axis->max_edges = new_max;
    }

    edges = axis->edges;
    edge  = edges + axis->num_edges;

    while ( edge > edges )
    {
      if ( top_to_bottom_hinting ? ( edge[-1].fpos > fpos )
                                 : ( edge[-1].fpos < fpos ) )
        break;

      /* we want the edge with same position and minor direction */
      /* to appear before those in the major one in the list     */
      if ( edge[-1].fpos == fpos && dir == axis->major_dir )
        break;

      edge[0] = edge[-1];
      edge--;
    }

    axis->num_edges++;

  Exit:
    *anedge = edge;
    return error;
  }

#ifdef FT_DEBUG_AUTOFIT

#include FT_CONFIG_STANDARD_LIBRARY_H

  /* The dump functions are used in the `ftgrid' demo program, too. */
#define AF_DUMP( varformat )          \
          do                          \
          {                           \
            if ( to_stdout )          \
              printf varformat;       \
            else                      \
              FT_TRACE7( varformat ); \
          } while ( 0 )

  static const char*
  af_dir_str( AF_Direction  dir )
  {
    const char*  result;

    switch ( dir )
    {
    case AF_DIR_UP:
      result = "up";
      break;
    case AF_DIR_DOWN:
      result = "down";
      break;
    case AF_DIR_LEFT:
      result = "left";
      break;
    case AF_DIR_RIGHT:
      result = "right";
      break;
    default:
      result = "none";
    }

    return result;
  }

#define AF_INDEX_NUM( ptr, base )  (int)( (ptr) ? ( (ptr) - (base) ) : -1 )

  static char*
  af_print_idx( char* p,
                int   idx )
  {
    if ( idx == -1 )
    {
      p[0] = '-';
      p[1] = '-';
      p[2] = '\0';
    }
    else
      ft_sprintf( p, "%d", idx );

    return p;
  }

  static int
  af_get_segment_index( AF_GlyphHints  hints,
                        int            point_idx,
                        int            dimension )
  {
    AF_AxisHints  axis     = &hints->axis[dimension];
    AF_Point      point    = hints->points + point_idx;
    AF_Segment    segments = axis->segments;
    AF_Segment    limit    = segments + axis->num_segments;
    AF_Segment    segment;

    for ( segment = segments; segment < limit; segment++ )
    {
      if ( segment->first <= segment->last )
      {
        if ( point >= segment->first && point <= segment->last )
          break;
      }
      else
      {
        AF_Point  p = segment->first;

        for (;;)
        {
          if ( point == p )
            goto Exit;

          if ( p == segment->last )
            break;

          p = p->next;
        }
      }
    }

  Exit:
    if ( segment == limit )
      return -1;

    return (int)( segment - segments );
  }

  static int
  af_get_edge_index( AF_GlyphHints  hints,
                     int            segment_idx,
                     int            dimension )
  {
    AF_AxisHints  axis    = &hints->axis[dimension];
    AF_Edge       edges   = axis->edges;
    AF_Segment    segment = axis->segments + segment_idx;

    return segment_idx == -1 ? -1 : AF_INDEX_NUM( segment->edge, edges );
  }

  static int
  af_get_strong_edge_index( AF_GlyphHints  hints,
                            AF_Edge*       strong_edges,
                            int            dimension )
  {
    AF_AxisHints  axis  = &hints->axis[dimension];
    AF_Edge       edges = axis->edges;

    return AF_INDEX_NUM( strong_edges[dimension], edges );
  }

#ifdef __cplusplus
  extern "C" {
#endif
  void
  af_glyph_hints_dump_points( AF_GlyphHints  hints,
                              FT_Bool        to_stdout )
  {
    AF_Point   points  = hints->points;
    AF_Point   limit   = points + hints->num_points;
    AF_Point*  contour = hints->contours;
    AF_Point*  climit  = contour + hints->num_contours;
    AF_Point   point;

    AF_DUMP(( "Table of points:\n" ));

    if ( hints->num_points )
    {
      AF_DUMP(( "  index  hedge  hseg  vedge  vseg  flags "
             /* "  XXXXX  XXXXX XXXXX  XXXXX XXXXX  XXXXXX" */
                "  xorg  yorg  xscale  yscale   xfit    yfit "
             /* " XXXXX XXXXX XXXX.XX XXXX.XX XXXX.XX XXXX.XX" */
                "  hbef  haft  vbef  vaft" ));
             /* " XXXXX XXXXX XXXXX XXXXX" */
    }
    else
      AF_DUMP(( "  (none)\n" ));

    for ( point = points; point < limit; point++ )
    {
      int  point_idx     = AF_INDEX_NUM( point, points );
      int  segment_idx_0 = af_get_segment_index( hints, point_idx, 0 );
      int  segment_idx_1 = af_get_segment_index( hints, point_idx, 1 );

      char  buf1[16], buf2[16], buf3[16], buf4[16];
      char  buf5[16], buf6[16], buf7[16], buf8[16];

      /* insert extra newline at the beginning of a contour */
      if ( contour < climit && *contour == point )
      {
        AF_DUMP(( "\n" ));
        contour++;
      }

      AF_DUMP(( "  %5d  %5s %5s  %5s %5s  %s"
                " %5d %5d %7.2f %7.2f %7.2f %7.2f"
                " %5s %5s %5s %5s\n",
                point_idx,
                af_print_idx( buf1,
                              af_get_edge_index( hints, segment_idx_1, 1 ) ),
                af_print_idx( buf2, segment_idx_1 ),
                af_print_idx( buf3,
                              af_get_edge_index( hints, segment_idx_0, 0 ) ),
                af_print_idx( buf4, segment_idx_0 ),
                ( point->flags & AF_FLAG_NEAR )
                  ? " near "
                  : ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
                    ? " weak "
                    : "strong",

                point->fx,
                point->fy,
                point->ox / 64.0,
                point->oy / 64.0,
                point->x / 64.0,
                point->y / 64.0,

                af_print_idx( buf5, af_get_strong_edge_index( hints,
                                                              point->before,
                                                              1 ) ),
                af_print_idx( buf6, af_get_strong_edge_index( hints,
                                                              point->after,
                                                              1 ) ),
                af_print_idx( buf7, af_get_strong_edge_index( hints,
                                                              point->before,
                                                              0 ) ),
                af_print_idx( buf8, af_get_strong_edge_index( hints,
                                                              point->after,
                                                              0 ) ) ));
    }
    AF_DUMP(( "\n" ));
  }
#ifdef __cplusplus
  }
#endif

  static const char*
  af_edge_flags_to_string( FT_UInt  flags )
  {
    static char  temp[32];
    int          pos = 0;

    if ( flags & AF_EDGE_ROUND )
    {
      ft_memcpy( temp + pos, "round", 5 );
      pos += 5;
    }
    if ( flags & AF_EDGE_SERIF )
    {
      if ( pos > 0 )
        temp[pos++] = ' ';
      ft_memcpy( temp + pos, "serif", 5 );
      pos += 5;
    }
    if ( pos == 0 )
      return "normal";

    temp[pos] = '\0';

    return temp;
  }

  /* Dump the array of linked segments. */

#ifdef __cplusplus
  extern "C" {
#endif
  void
  af_glyph_hints_dump_segments( AF_GlyphHints  hints,
                                FT_Bool        to_stdout )
  {
    FT_Int  dimension;

    for ( dimension = 1; dimension >= 0; dimension-- )
    {
      AF_AxisHints  axis     = &hints->axis[dimension];
      AF_Point      points   = hints->points;
      AF_Edge       edges    = axis->edges;
      AF_Segment    segments = axis->segments;
      AF_Segment    limit    = segments + axis->num_segments;
      AF_Segment    seg;

      char  buf1[16], buf2[16], buf3[16];

      AF_DUMP(( "Table of %s segments:\n",
                dimension == AF_DIMENSION_HORZ ? "vertical"
                                               : "horizontal" ));
      if ( axis->num_segments )
      {
        AF_DUMP(( "  index   pos   delta   dir   from   to "
               /* "  XXXXX  XXXXX  XXXXX  XXXXX  XXXX  XXXX" */
                  "  link  serif  edge"
               /* "  XXXX  XXXXX  XXXX" */
                  "  height  extra     flags\n" ));
               /* "  XXXXXX  XXXXX  XXXXXXXXXXX" */
      }
      else
        AF_DUMP(( "  (none)\n" ));

      for ( seg = segments; seg < limit; seg++ )
        AF_DUMP(( "  %5d  %5d  %5d  %5s  %4d  %4d"
                  "  %4s  %5s  %4s"
                  "  %6d  %5d  %11s\n",
                  AF_INDEX_NUM( seg, segments ),
                  seg->pos,
                  seg->delta,
                  af_dir_str( (AF_Direction)seg->dir ),
                  AF_INDEX_NUM( seg->first, points ),
                  AF_INDEX_NUM( seg->last, points ),

                  af_print_idx( buf1, AF_INDEX_NUM( seg->link, segments ) ),
                  af_print_idx( buf2, AF_INDEX_NUM( seg->serif, segments ) ),
                  af_print_idx( buf3, AF_INDEX_NUM( seg->edge, edges ) ),

                  seg->height,
                  seg->height - ( seg->max_coord - seg->min_coord ),
                  af_edge_flags_to_string( seg->flags ) ));
      AF_DUMP(( "\n" ));
    }
  }
#ifdef __cplusplus
  }
#endif

  /* Fetch number of segments. */

#ifdef __cplusplus
  extern "C" {
#endif
  FT_Error
  af_glyph_hints_get_num_segments( AF_GlyphHints  hints,
                                   FT_Int         dimension,
                                   FT_Int*        num_segments )
  {
    AF_Dimension  dim;
    AF_AxisHints  axis;

    dim = ( dimension == 0 ) ? AF_DIMENSION_HORZ : AF_DIMENSION_VERT;

    axis          = &hints->axis[dim];
    *num_segments = axis->num_segments;

    return FT_Err_Ok;
  }
#ifdef __cplusplus
  }
#endif

  /* Fetch offset of segments into user supplied offset array. */

#ifdef __cplusplus
  extern "C" {
#endif
  FT_Error
  af_glyph_hints_get_segment_offset( AF_GlyphHints  hints,
                                     FT_Int         dimension,
                                     FT_Int         idx,
                                     FT_Pos        *offset,
                                     FT_Bool       *is_blue,
                                     FT_Pos        *blue_offset )
  {
    AF_Dimension  dim;
    AF_AxisHints  axis;
    AF_Segment    seg;

    if ( !offset )
      return FT_THROW( Invalid_Argument );

    dim = ( dimension == 0 ) ? AF_DIMENSION_HORZ : AF_DIMENSION_VERT;

    axis = &hints->axis[dim];

    if ( idx < 0 || idx >= axis->num_segments )
      return FT_THROW( Invalid_Argument );

    seg      = &axis->segments[idx];
    *offset  = ( dim == AF_DIMENSION_HORZ ) ? seg->first->fx
                                            : seg->first->fy;
    if ( seg->edge )
      *is_blue = FT_BOOL( seg->edge->blue_edge );
    else
      *is_blue = FALSE;

    if ( *is_blue )
      *blue_offset = seg->edge->blue_edge->org;
    else
      *blue_offset = 0;

    return FT_Err_Ok;
  }
#ifdef __cplusplus
  }
#endif

  /* Dump the array of linked edges. */

#ifdef __cplusplus
  extern "C" {
#endif
  void
  af_glyph_hints_dump_edges( AF_GlyphHints  hints,
                             FT_Bool        to_stdout )
  {
    FT_Int  dimension;

    for ( dimension = 1; dimension >= 0; dimension-- )
    {
      AF_AxisHints  axis  = &hints->axis[dimension];
      AF_Edge       edges = axis->edges;
      AF_Edge       limit = edges + axis->num_edges;
      AF_Edge       edge;

      char  buf1[16], buf2[16];

      /*
       * note: AF_DIMENSION_HORZ corresponds to _vertical_ edges
       *       since they have a constant X coordinate.
       */
      if ( dimension == AF_DIMENSION_HORZ )
        AF_DUMP(( "Table of %s edges (1px=%.2fu, 10u=%.2fpx):\n",
                  "vertical",
                  65536.0 * 64.0 / hints->x_scale,
                  10.0 * hints->x_scale / 65536.0 / 64.0 ));
      else
        AF_DUMP(( "Table of %s edges (1px=%.2fu, 10u=%.2fpx):\n",
                  "horizontal",
                  65536.0 * 64.0 / hints->y_scale,
                  10.0 * hints->y_scale / 65536.0 / 64.0 ));

      if ( axis->num_edges )
      {
        AF_DUMP(( "  index    pos     dir   link  serif"
               /* "  XXXXX  XXXX.XX  XXXXX  XXXX  XXXXX" */
                  "  blue    opos     pos       flags\n" ));
               /* "    X   XXXX.XX  XXXX.XX  XXXXXXXXXXX" */
      }
      else
        AF_DUMP(( "  (none)\n" ));

      for ( edge = edges; edge < limit; edge++ )
        AF_DUMP(( "  %5d  %7.2f  %5s  %4s  %5s"
                  "    %c   %7.2f  %7.2f  %11s\n",
                  AF_INDEX_NUM( edge, edges ),
                  (int)edge->opos / 64.0,
                  af_dir_str( (AF_Direction)edge->dir ),
                  af_print_idx( buf1, AF_INDEX_NUM( edge->link, edges ) ),
                  af_print_idx( buf2, AF_INDEX_NUM( edge->serif, edges ) ),

                  edge->blue_edge ? 'y' : 'n',
                  edge->opos / 64.0,
                  edge->pos / 64.0,
                  af_edge_flags_to_string( edge->flags ) ));
      AF_DUMP(( "\n" ));
    }
  }
#ifdef __cplusplus
  }
#endif

#undef AF_DUMP

#endif /* !FT_DEBUG_AUTOFIT */

  /* Compute the direction value of a given vector. */

  FT_LOCAL_DEF( AF_Direction )
  af_direction_compute( FT_Pos  dx,
                        FT_Pos  dy )
  {
    FT_Pos        ll, ss;  /* long and short arm lengths */
    AF_Direction  dir;     /* candidate direction        */

    if ( dy >= dx )
    {
      if ( dy >= -dx )
      {
        dir = AF_DIR_UP;
        ll  = dy;
        ss  = dx;
      }
      else
      {
        dir = AF_DIR_LEFT;
        ll  = -dx;
        ss  = dy;
      }
    }
    else /* dy < dx */
    {
      if ( dy >= -dx )
      {
        dir = AF_DIR_RIGHT;
        ll  = dx;
        ss  = dy;
      }
      else
      {
        dir = AF_DIR_DOWN;
        ll  = -dy;
        ss  = dx;
      }
    }

    /* return no direction if arm lengths do not differ enough       */
    /* (value 14 is heuristic, corresponding to approx. 4.1 degrees) */
    /* the long arm is never negative                                */
    if ( ll <= 14 * FT_ABS( ss ) )
      dir = AF_DIR_NONE;

    return dir;
  }

  FT_LOCAL_DEF( void )
  af_glyph_hints_init( AF_GlyphHints  hints,
                       FT_Memory      memory )
  {
    /* no need to initialize the embedded items */
    FT_MEM_ZERO( hints, sizeof ( *hints ) - sizeof ( hints->embedded ) );
    hints->memory = memory;
  }

  FT_LOCAL_DEF( void )
  af_glyph_hints_done( AF_GlyphHints  hints )
  {
    FT_Memory  memory;
    int        dim;

    if ( !( hints && hints->memory ) )
      return;

    memory = hints->memory;

    /*
     * note that we don't need to free the segment and edge
     * buffers since they are really within the hints->points array
     */
    for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
    {
      AF_AxisHints  axis = &hints->axis[dim];

      axis->num_segments = 0;
      axis->max_segments = 0;
      if ( axis->segments != axis->embedded.segments )
        FT_FREE( axis->segments );

      axis->num_edges = 0;
      axis->max_edges = 0;
      if ( axis->edges != axis->embedded.edges )
        FT_FREE( axis->edges );
    }

    if ( hints->contours != hints->embedded.contours )
      FT_FREE( hints->contours );
    hints->max_contours = 0;
    hints->num_contours = 0;

    if ( hints->points != hints->embedded.points )
      FT_FREE( hints->points );
    hints->max_points = 0;
    hints->num_points = 0;

    hints->memory = NULL;
  }

  /* Reset metrics. */

  FT_LOCAL_DEF( void )
  af_glyph_hints_rescale( AF_GlyphHints    hints,
                          AF_StyleMetrics  metrics )
  {
    hints->metrics      = metrics;
    hints->scaler_flags = metrics->scaler.flags;
  }

  /* Recompute all AF_Point in AF_GlyphHints from the definitions */
  /* in a source outline.                                         */

  FT_LOCAL_DEF( FT_Error )
  af_glyph_hints_reload( AF_GlyphHints  hints,
                         FT_Outline*    outline )
  {
    FT_Error   error   = FT_Err_Ok;
    AF_Point   points;
    FT_UInt    old_max, new_max;
    FT_Fixed   x_scale = hints->x_scale;
    FT_Fixed   y_scale = hints->y_scale;
    FT_Pos     x_delta = hints->x_delta;
    FT_Pos     y_delta = hints->y_delta;
    FT_Memory  memory  = hints->memory;

    hints->num_points   = 0;
    hints->num_contours = 0;

    hints->axis[0].num_segments = 0;
    hints->axis[0].num_edges    = 0;
    hints->axis[1].num_segments = 0;
    hints->axis[1].num_edges    = 0;

    /* first of all, reallocate the contours array if necessary */
    new_max = (FT_UInt)outline->n_contours;
    old_max = (FT_UInt)hints->max_contours;

    if ( new_max <= AF_CONTOURS_EMBEDDED )
    {
      if ( !hints->contours )
      {
        hints->contours     = hints->embedded.contours;
        hints->max_contours = AF_CONTOURS_EMBEDDED;
      }
    }
    else if ( new_max > old_max )
    {
      if ( hints->contours == hints->embedded.contours )
        hints->contours = NULL;

      new_max = ( new_max + 3 ) & ~3U; /* round up to a multiple of 4 */

      if ( FT_RENEW_ARRAY( hints->contours, old_max, new_max ) )
        goto Exit;

      hints->max_contours = (FT_Int)new_max;
    }

    /*
     * then reallocate the points arrays if necessary --
     * note that we reserve two additional point positions, used to
     * hint metrics appropriately
     */
    new_max = (FT_UInt)( outline->n_points + 2 );
    old_max = (FT_UInt)hints->max_points;

    if ( new_max <= AF_POINTS_EMBEDDED )
    {
      if ( !hints->points )
      {
        hints->points     = hints->embedded.points;
        hints->max_points = AF_POINTS_EMBEDDED;
      }
    }
    else if ( new_max > old_max )
    {
      if ( hints->points == hints->embedded.points )
        hints->points = NULL;

      new_max = ( new_max + 2 + 7 ) & ~7U; /* round up to a multiple of 8 */

      if ( FT_RENEW_ARRAY( hints->points, old_max, new_max ) )
        goto Exit;

      hints->max_points = (FT_Int)new_max;
    }

    hints->num_points   = outline->n_points;
    hints->num_contours = outline->n_contours;

    /* We can't rely on the value of `FT_Outline.flags' to know the fill   */
    /* direction used for a glyph, given that some fonts are broken (e.g., */
    /* the Arphic ones).  We thus recompute it each time we need to.       */
    /*                                                                     */
    hints->axis[AF_DIMENSION_HORZ].major_dir = AF_DIR_UP;
    hints->axis[AF_DIMENSION_VERT].major_dir = AF_DIR_LEFT;

    if ( FT_Outline_Get_Orientation( outline ) == FT_ORIENTATION_POSTSCRIPT )
    {
      hints->axis[AF_DIMENSION_HORZ].major_dir = AF_DIR_DOWN;
      hints->axis[AF_DIMENSION_VERT].major_dir = AF_DIR_RIGHT;
    }

    hints->x_scale = x_scale;
    hints->y_scale = y_scale;
    hints->x_delta = x_delta;
    hints->y_delta = y_delta;

    hints->xmin_delta = 0;
    hints->xmax_delta = 0;

    points = hints->points;
    if ( hints->num_points == 0 )
      goto Exit;

    {
      AF_Point  point;
      AF_Point  point_limit = points + hints->num_points;

      /* value 20 in `near_limit' is heuristic */
      FT_UInt  units_per_em = hints->metrics->scaler.face->units_per_EM;
      FT_Int   near_limit   = 20 * units_per_em / 2048;

      /* compute coordinates & Bezier flags, next and prev */
      {
        FT_Vector*  vec           = outline->points;
        char*       tag           = outline->tags;
        FT_Short    endpoint      = outline->contours[0];
        AF_Point    end           = points + endpoint;
        AF_Point    prev          = end;
        FT_Int      contour_index = 0;

        for ( point = points; point < point_limit; point++, vec++, tag++ )
        {
          FT_Pos  out_x, out_y;

          point->in_dir  = (FT_Char)AF_DIR_NONE;
          point->out_dir = (FT_Char)AF_DIR_NONE;

          point->fx = (FT_Short)vec->x;
          point->fy = (FT_Short)vec->y;
          point->ox = point->x = FT_MulFix( vec->x, x_scale ) + x_delta;
          point->oy = point->y = FT_MulFix( vec->y, y_scale ) + y_delta;

          end->fx = (FT_Short)outline->points[endpoint].x;
          end->fy = (FT_Short)outline->points[endpoint].y;

          switch ( FT_CURVE_TAG( *tag ) )
          {
          case FT_CURVE_TAG_CONIC:
            point->flags = AF_FLAG_CONIC;
            break;
          case FT_CURVE_TAG_CUBIC:
            point->flags = AF_FLAG_CUBIC;
            break;
          default:
            point->flags = AF_FLAG_NONE;
          }

          out_x = point->fx - prev->fx;
          out_y = point->fy - prev->fy;

          if ( FT_ABS( out_x ) + FT_ABS( out_y ) < near_limit )
            prev->flags |= AF_FLAG_NEAR;

          point->prev = prev;
          prev->next  = point;
          prev        = point;

          if ( point == end )
          {
            if ( ++contour_index < outline->n_contours )
            {
              endpoint = outline->contours[contour_index];
              end      = points + endpoint;
              prev     = end;
            }
          }

#ifdef FT_DEBUG_AUTOFIT
          point->before[0] = NULL;
          point->before[1] = NULL;
          point->after[0]  = NULL;
          point->after[1]  = NULL;
#endif

        }
      }

      /* set up the contours array */
      {
        AF_Point*  contour       = hints->contours;
        AF_Point*  contour_limit = contour + hints->num_contours;
        short*     end           = outline->contours;
        short      idx           = 0;

        for ( ; contour < contour_limit; contour++, end++ )
        {
          contour[0] = points + idx;
          idx        = (short)( end[0] + 1 );
        }
      }

      {
        /*
         * Compute directions of `in' and `out' vectors.
         *
         * Note that distances between points that are very near to each
         * other are accumulated.  In other words, the auto-hinter either
         * prepends the small vectors between near points to the first
         * non-near vector, or the sum of small vector lengths exceeds a
         * threshold, thus `grouping' the small vectors.  All intermediate
         * points are tagged as weak; the directions are adjusted also to
         * be equal to the accumulated one.
         */

        FT_Int  near_limit2 = 2 * near_limit - 1;

        AF_Point*  contour;
        AF_Point*  contour_limit = hints->contours + hints->num_contours;

        for ( contour = hints->contours; contour < contour_limit; contour++ )
        {
          AF_Point  first = *contour;
          AF_Point  next, prev, curr;

          FT_Pos  out_x, out_y;

          /* since the first point of a contour could be part of a */
          /* series of near points, go backwards to find the first */
          /* non-near point and adjust `first'                     */

          point = first;
          prev  = first->prev;

          while ( prev != first )
          {
            out_x = point->fx - prev->fx;
            out_y = point->fy - prev->fy;

            /*
             * We use Taxicab metrics to measure the vector length.
             *
             * Note that the accumulated distances so far could have the
             * opposite direction of the distance measured here.  For this
             * reason we use `near_limit2' for the comparison to get a
             * non-near point even in the worst case.
             */
            if ( FT_ABS( out_x ) + FT_ABS( out_y ) >= near_limit2 )
              break;

            point = prev;
            prev  = prev->prev;
          }

          /* adjust first point */
          first = point;

          /* now loop over all points of the contour to get */
          /* `in' and `out' vector directions               */

          curr = first;

          /*
           * We abuse the `u' and `v' fields to store index deltas to the
           * next and previous non-near point, respectively.
           *
           * To avoid problems with not having non-near points, we point to
           * `first' by default as the next non-near point.
           *
           */
          curr->u  = (FT_Pos)( first - curr );
          first->v = -curr->u;

          out_x = 0;
          out_y = 0;

          next = first;
          do
          {
            AF_Direction  out_dir;

            point = next;
            next  = point->next;

            out_x += next->fx - point->fx;
            out_y += next->fy - point->fy;

            if ( FT_ABS( out_x ) + FT_ABS( out_y ) < near_limit )
            {
              next->flags |= AF_FLAG_WEAK_INTERPOLATION;
              continue;
            }

            curr->u = (FT_Pos)( next - curr );
            next->v = -curr->u;

            out_dir = af_direction_compute( out_x, out_y );

            /* adjust directions for all points inbetween; */
            /* the loop also updates position of `curr'    */
            curr->out_dir = (FT_Char)out_dir;
            for ( curr = curr->next; curr != next; curr = curr->next )
            {
              curr->in_dir  = (FT_Char)out_dir;
              curr->out_dir = (FT_Char)out_dir;
            }
            next->in_dir = (FT_Char)out_dir;

            curr->u  = (FT_Pos)( first - curr );
            first->v = -curr->u;

            out_x = 0;
            out_y = 0;

          } while ( next != first );
        }

        /*
         * The next step is to `simplify' an outline's topology so that we
         * can identify local extrema more reliably: A series of
         * non-horizontal or non-vertical vectors pointing into the same
         * quadrant are handled as a single, long vector.  From a
         * topological point of the view, the intermediate points are of no
         * interest and thus tagged as weak.
         */

        for ( point = points; point < point_limit; point++ )
        {
          if ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
            continue;

          if ( point->in_dir  == AF_DIR_NONE &&
               point->out_dir == AF_DIR_NONE )
          {
            /* check whether both vectors point into the same quadrant */

            FT_Pos  in_x, in_y;
            FT_Pos  out_x, out_y;

            AF_Point  next_u = point + point->u;
            AF_Point  prev_v = point + point->v;

            in_x = point->fx - prev_v->fx;
            in_y = point->fy - prev_v->fy;

            out_x = next_u->fx - point->fx;
            out_y = next_u->fy - point->fy;

            if ( ( in_x ^ out_x ) >= 0 && ( in_y ^ out_y ) >= 0 )
            {
              /* yes, so tag current point as weak */
              /* and update index deltas           */

              point->flags |= AF_FLAG_WEAK_INTERPOLATION;

              prev_v->u = (FT_Pos)( next_u - prev_v );
              next_u->v = -prev_v->u;
            }
          }
        }

        /*
         * Finally, check for remaining weak points.  Everything else not
         * collected in edges so far is then implicitly classified as strong
         * points.
         */

        for ( point = points; point < point_limit; point++ )
        {
          if ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
            continue;

          if ( point->flags & AF_FLAG_CONTROL )
          {
            /* control points are always weak */
          Is_Weak_Point:
            point->flags |= AF_FLAG_WEAK_INTERPOLATION;
          }
          else if ( point->out_dir == point->in_dir )
          {
            if ( point->out_dir != AF_DIR_NONE )
            {
              /* current point lies on a horizontal or          */
              /* vertical segment (but doesn't start or end it) */
              goto Is_Weak_Point;
            }

            {
              AF_Point  next_u = point + point->u;
              AF_Point  prev_v = point + point->v;

              if ( ft_corner_is_flat( point->fx  - prev_v->fx,
                                      point->fy  - prev_v->fy,
                                      next_u->fx - point->fx,
                                      next_u->fy - point->fy ) )
              {
                /* either the `in' or the `out' vector is much more  */
                /* dominant than the other one, so tag current point */
                /* as weak and update index deltas                   */

                prev_v->u = (FT_Pos)( next_u - prev_v );
                next_u->v = -prev_v->u;

                goto Is_Weak_Point;
              }
            }
          }
          else if ( point->in_dir == -point->out_dir )
          {
            /* current point forms a spike */
            goto Is_Weak_Point;
          }
        }
      }
    }

  Exit:
    return error;
  }

  /* Store the hinted outline in an FT_Outline structure. */

  FT_LOCAL_DEF( void )
  af_glyph_hints_save( AF_GlyphHints  hints,
                       FT_Outline*    outline )
  {
    AF_Point    point = hints->points;
    AF_Point    limit = point + hints->num_points;
    FT_Vector*  vec   = outline->points;
    char*       tag   = outline->tags;

    for ( ; point < limit; point++, vec++, tag++ )
    {
      vec->x = point->x;
      vec->y = point->y;

      if ( point->flags & AF_FLAG_CONIC )
        tag[0] = FT_CURVE_TAG_CONIC;
      else if ( point->flags & AF_FLAG_CUBIC )
        tag[0] = FT_CURVE_TAG_CUBIC;
      else
        tag[0] = FT_CURVE_TAG_ON;
    }
  }

  /****************************************************************
   *
   *                     EDGE POINT GRID-FITTING
   *
   ****************************************************************/

  /* Align all points of an edge to the same coordinate value, */
  /* either horizontally or vertically.                        */

  FT_LOCAL_DEF( void )
  af_glyph_hints_align_edge_points( AF_GlyphHints  hints,
                                    AF_Dimension   dim )
  {
    AF_AxisHints  axis          = & hints->axis[dim];
    AF_Segment    segments      = axis->segments;
    AF_Segment    segment_limit = segments + axis->num_segments;
    AF_Segment    seg;

    if ( dim == AF_DIMENSION_HORZ )
    {
      for ( seg = segments; seg < segment_limit; seg++ )
      {
        AF_Edge   edge = seg->edge;
        AF_Point  point, first, last;

        if ( !edge )
          continue;

        first = seg->first;
        last  = seg->last;
        point = first;
        for (;;)
        {
          point->x      = edge->pos;
          point->flags |= AF_FLAG_TOUCH_X;

          if ( point == last )
            break;

          point = point->next;
        }
      }
    }
    else
    {
      for ( seg = segments; seg < segment_limit; seg++ )
      {
        AF_Edge   edge = seg->edge;
        AF_Point  point, first, last;

        if ( !edge )
          continue;

        first = seg->first;
        last  = seg->last;
        point = first;
        for (;;)
        {
          point->y      = edge->pos;
          point->flags |= AF_FLAG_TOUCH_Y;

          if ( point == last )
            break;

          point = point->next;
        }
      }
    }
  }

  /****************************************************************
   *
   *                    STRONG POINT INTERPOLATION
   *
   ****************************************************************/

  /* Hint the strong points -- this is equivalent to the TrueType `IP' */
  /* hinting instruction.                                              */

  FT_LOCAL_DEF( void )
  af_glyph_hints_align_strong_points( AF_GlyphHints  hints,
                                      AF_Dimension   dim )
  {
    AF_Point      points      = hints->points;
    AF_Point      point_limit = points + hints->num_points;
    AF_AxisHints  axis        = &hints->axis[dim];
    AF_Edge       edges       = axis->edges;
    AF_Edge       edge_limit  = edges + axis->num_edges;
    FT_UInt       touch_flag;

    if ( dim == AF_DIMENSION_HORZ )
      touch_flag = AF_FLAG_TOUCH_X;
    else
      touch_flag  = AF_FLAG_TOUCH_Y;

    if ( edges < edge_limit )
    {
      AF_Point  point;
      AF_Edge   edge;

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

        if ( point->flags & touch_flag )
          continue;

        /* if this point is candidate to weak interpolation, we       */
        /* interpolate it after all strong points have been processed */

        if ( ( point->flags & AF_FLAG_WEAK_INTERPOLATION ) )
          continue;

        if ( dim == AF_DIMENSION_VERT )
        {
          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 );

#ifdef FT_DEBUG_AUTOFIT
          point->before[dim] = edge;
          point->after[dim]  = NULL;
#endif

          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 );

#ifdef FT_DEBUG_AUTOFIT
          point->before[dim] = NULL;
          point->after[dim]  = edge;
#endif

          goto Store_Point;
        }

        {
          FT_PtrDist  min, max, mid;
          FT_Pos      fpos;

          /* find enclosing edges */
          min = 0;
          max = edge_limit - edges;

#if 1
          /* for a small number of edges, a linear search is better */
          if ( max <= 8 )
          {
            FT_PtrDist  nn;

            for ( nn = 0; nn < max; nn++ )
              if ( edges[nn].fpos >= u )
                break;

            if ( edges[nn].fpos == u )
            {
              u = edges[nn].pos;
              goto Store_Point;
            }
            min = nn;
          }
          else
#endif
          while ( min < max )
          {
            mid  = ( max + min ) >> 1;
            edge = edges + mid;
            fpos = edge->fpos;

            if ( u < fpos )
              max = mid;
            else if ( u > fpos )
              min = mid + 1;
            else
            {
              /* we are on the edge */
              u = edge->pos;

#ifdef FT_DEBUG_AUTOFIT
              point->before[dim] = NULL;
              point->after[dim]  = NULL;
#endif

              goto Store_Point;
            }
          }

          /* point is not on an edge */
          {
            AF_Edge  before = edges + min - 1;
            AF_Edge  after  = edges + min + 0;

#ifdef FT_DEBUG_AUTOFIT
            point->before[dim] = before;
            point->after[dim]  = after;
#endif

            /* assert( before && after && before != after ) */
            if ( before->scale == 0 )
              before->scale = FT_DivFix( after->pos - before->pos,
                                         after->fpos - before->fpos );

            u = before->pos + FT_MulFix( fu - before->fpos,
                                         before->scale );
          }
        }

      Store_Point:
        /* save the point position */
        if ( dim == AF_DIMENSION_HORZ )
          point->x = u;
        else
          point->y = u;

        point->flags |= touch_flag;
      }
    }
  }

  /****************************************************************
   *
   *                    WEAK POINT INTERPOLATION
   *
   ****************************************************************/

  /* Shift the original coordinates of all points between `p1' and */
  /* `p2' to get hinted coordinates, using the same difference as  */
  /* given by `ref'.                                               */

  static void
  af_iup_shift( AF_Point  p1,
                AF_Point  p2,
                AF_Point  ref )
  {
    AF_Point  p;
    FT_Pos    delta = ref->u - ref->v;

    if ( delta == 0 )
      return;

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

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

  /* Interpolate the original coordinates of all points between `p1' and  */
  /* `p2' to get hinted coordinates, using `ref1' and `ref2' as the       */
  /* reference points.  The `u' and `v' members are the current and       */
  /* original coordinate values, respectively.                            */
  /*                                                                      */
  /* Details can be found in the TrueType bytecode specification.         */

  static void
  af_iup_interp( AF_Point  p1,
                 AF_Point  p2,
                 AF_Point  ref1,
                 AF_Point  ref2 )
  {
    AF_Point  p;
    FT_Pos    u, v1, v2, u1, u2, d1, d2;

    if ( p1 > p2 )
      return;

    if ( ref1->v > ref2->v )
    {
      p    = ref1;
      ref1 = ref2;
      ref2 = p;
    }

    v1 = ref1->v;
    v2 = ref2->v;
    u1 = ref1->u;
    u2 = ref2->u;
    d1 = u1 - v1;
    d2 = u2 - v2;

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

        if ( u <= v1 )
          u += d1;
        else if ( u >= v2 )
          u += d2;
        else
          u = u1;

        p->u = u;
      }
    }
    else
    {
      FT_Fixed  scale = FT_DivFix( u2 - u1, v2 - v1 );

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

        if ( u <= v1 )
          u += d1;
        else if ( u >= v2 )
          u += d2;
        else
          u = u1 + FT_MulFix( u - v1, scale );

        p->u = u;
      }
    }
  }

  /* Hint the weak points -- this is equivalent to the TrueType `IUP' */
  /* hinting instruction.                                             */

  FT_LOCAL_DEF( void )
  af_glyph_hints_align_weak_points( AF_GlyphHints  hints,
                                    AF_Dimension   dim )
  {
    AF_Point   points        = hints->points;
    AF_Point   point_limit   = points + hints->num_points;
    AF_Point*  contour       = hints->contours;
    AF_Point*  contour_limit = contour + hints->num_contours;
    FT_UInt    touch_flag;
    AF_Point   point;
    AF_Point   end_point;
    AF_Point   first_point;

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

    if ( dim == AF_DIMENSION_HORZ )
    {
      touch_flag = AF_FLAG_TOUCH_X;

      for ( point = points; point < point_limit; point++ )
      {
        point->u = point->x;
        point->v = point->ox;
      }
    }
    else
    {
      touch_flag = AF_FLAG_TOUCH_Y;

      for ( point = points; point < point_limit; point++ )
      {
        point->u = point->y;
        point->v = point->oy;
      }
    }

    for ( ; contour < contour_limit; contour++ )
    {
      AF_Point  first_touched, last_touched;

      point       = *contour;
      end_point   = point->prev;
      first_point = point;

      /* find first touched point */
      for (;;)
      {
        if ( point > end_point )  /* no touched point in contour */
          goto NextContour;

        if ( point->flags & touch_flag )
          break;

        point++;
      }

      first_touched = point;

      for (;;)
      {
        FT_ASSERT( point <= end_point                 &&
                   ( point->flags & touch_flag ) != 0 );

        /* skip any touched neighbours */
        while ( point < end_point                    &&
                ( point[1].flags & touch_flag ) != 0 )
          point++;

        last_touched = point;

        /* find the next touched point, if any */
        point++;
        for (;;)
        {
          if ( point > end_point )
            goto EndContour;

          if ( ( point->flags & touch_flag ) != 0 )
            break;

          point++;
        }

        /* interpolate between last_touched and point */
        af_iup_interp( last_touched + 1, point - 1,
                       last_touched, point );
      }

    EndContour:
      /* special case: only one point was touched */
      if ( last_touched == first_touched )
        af_iup_shift( first_point, end_point, first_touched );

      else /* interpolate the last part */
      {
        if ( last_touched < end_point )
          af_iup_interp( last_touched + 1, end_point,
                         last_touched, first_touched );

        if ( first_touched > points )
          af_iup_interp( first_point, first_touched - 1,
                         last_touched, first_touched );
      }

    NextContour:
      ;
    }

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

#ifdef AF_CONFIG_OPTION_USE_WARPER

  /* Apply (small) warp scale and warp delta for given dimension. */

  FT_LOCAL_DEF( void )
  af_glyph_hints_scale_dim( AF_GlyphHints  hints,
                            AF_Dimension   dim,
                            FT_Fixed       scale,
                            FT_Pos         delta )
  {
    AF_Point  points       = hints->points;
    AF_Point  points_limit = points + hints->num_points;
    AF_Point  point;

    if ( dim == AF_DIMENSION_HORZ )
    {
      for ( point = points; point < points_limit; point++ )
        point->x = FT_MulFix( point->fx, scale ) + delta;
    }
    else
    {
      for ( point = points; point < points_limit; point++ )
        point->y = FT_MulFix( point->fy, scale ) + delta;
    }
  }

#endif /* AF_CONFIG_OPTION_USE_WARPER */

/* END */


text:
/****************************************************************************
 *
 * afshaper.c
 *
 *   HarfBuzz interface for accessing OpenType features (body).
 *
 * Copyright (C) 2013-2020 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_ADVANCES_H
#include "afglobal.h"
#include "aftypes.h"
#include "afshaper.h"

#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ

  /**************************************************************************
   *
   * The macro FT_COMPONENT is used in trace mode.  It is an implicit
   * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
   * messages during execution.
   */
#undef  FT_COMPONENT
#define FT_COMPONENT  afshaper

  /*
   * We use `sets' (in the HarfBuzz sense, which comes quite near to the
   * usual mathematical meaning) to manage both lookups and glyph indices.
   *
   * 1. For each coverage, collect lookup IDs in a set.  Note that an
   *    auto-hinter `coverage' is represented by one `feature', and a
   *    feature consists of an arbitrary number of (font specific) `lookup's
   *    that actually do the mapping job.  Please check the OpenType
   *    specification for more details on features and lookups.
   *
   * 2. Create glyph ID sets from the corresponding lookup sets.
   *
   * 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed
   *    with all lookups specific to the OpenType script activated.  It
   *    relies on the order of AF_DEFINE_STYLE_CLASS entries so that
   *    special coverages (like `oldstyle figures') don't get overwritten.
   *
   */

  /* load coverage tags */
#undef  COVERAGE
#define COVERAGE( name, NAME, description,             \
                  tag1, tag2, tag3, tag4 )             \
          static const hb_tag_t  name ## _coverage[] = \
          {                                            \
            HB_TAG( tag1, tag2, tag3, tag4 ),          \
            HB_TAG_NONE                                \
          };

#include "afcover.h"

  /* define mapping between coverage tags and AF_Coverage */
#undef  COVERAGE
#define COVERAGE( name, NAME, description, \
                  tag1, tag2, tag3, tag4 ) \
          name ## _coverage,

  static const hb_tag_t*  coverages[] =
  {
#include "afcover.h"

    NULL /* AF_COVERAGE_DEFAULT */
  };

  /* load HarfBuzz script tags */
#undef  SCRIPT
#define SCRIPT( s, S, d, h, H, ss )  h,

  static const hb_script_t  scripts[] =
  {
#include "afscript.h"
  };

  FT_Error
  af_shaper_get_coverage( AF_FaceGlobals  globals,
                          AF_StyleClass   style_class,
                          FT_UShort*      gstyles,
                          FT_Bool         default_script )
  {
    hb_face_t*  face;

    hb_set_t*  gsub_lookups = NULL; /* GSUB lookups for a given script */
    hb_set_t*  gsub_glyphs  = NULL; /* glyphs covered by GSUB lookups  */
    hb_set_t*  gpos_lookups = NULL; /* GPOS lookups for a given script */
    hb_set_t*  gpos_glyphs  = NULL; /* glyphs covered by GPOS lookups  */

    hb_script_t      script;
    const hb_tag_t*  coverage_tags;
    hb_tag_t         script_tags[] = { HB_TAG_NONE,
                                       HB_TAG_NONE,
                                       HB_TAG_NONE,
                                       HB_TAG_NONE };

    hb_codepoint_t  idx;
#ifdef FT_DEBUG_LEVEL_TRACE
    int             count;
#endif

    if ( !globals || !style_class || !gstyles )
      return FT_THROW( Invalid_Argument );

    face = hb_font_get_face( globals->hb_font );

    coverage_tags = coverages[style_class->coverage];
    script        = scripts[style_class->script];

    /* Convert a HarfBuzz script tag into the corresponding OpenType */
    /* tag or tags -- some Indic scripts like Devanagari have an old */
    /* and a new set of features.                                    */
    hb_ot_tags_from_script( script,
                            &script_tags[0],
                            &script_tags[1] );

    /* `hb_ot_tags_from_script' usually returns HB_OT_TAG_DEFAULT_SCRIPT */
    /* as the second tag.  We change that to HB_TAG_NONE except for the  */
    /* default script.                                                   */
    if ( default_script )
    {
      if ( script_tags[0] == HB_TAG_NONE )
        script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT;
      else
      {
        if ( script_tags[1] == HB_TAG_NONE )
          script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT;
        else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT )
          script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT;
      }
    }
    else
    {
      /* we use non-standard tags like `khms' for special purposes;       */
      /* HarfBuzz maps them to `DFLT', which we don't want to handle here */
      if ( script_tags[0] == HB_OT_TAG_DEFAULT_SCRIPT )
        goto Exit;

      if ( script_tags[1] == HB_OT_TAG_DEFAULT_SCRIPT )
        script_tags[1] = HB_TAG_NONE;
    }

    gsub_lookups = hb_set_create();
    hb_ot_layout_collect_lookups( face,
                                  HB_OT_TAG_GSUB,
                                  script_tags,
                                  NULL,
                                  coverage_tags,
                                  gsub_lookups );

    if ( hb_set_is_empty( gsub_lookups ) )
      goto Exit; /* nothing to do */

    FT_TRACE4(( "GSUB lookups (style `%s'):\n"
                " ",
                af_style_names[style_class->style] ));

#ifdef FT_DEBUG_LEVEL_TRACE
    count = 0;
#endif

    gsub_glyphs = hb_set_create();
    for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, &idx ); )
    {
#ifdef FT_DEBUG_LEVEL_TRACE
      FT_TRACE4(( " %d", idx ));
      count++;
#endif

      /* get output coverage of GSUB feature */
      hb_ot_layout_lookup_collect_glyphs( face,
                                          HB_OT_TAG_GSUB,
                                          idx,
                                          NULL,
                                          NULL,
                                          NULL,
                                          gsub_glyphs );
    }

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( !count )
      FT_TRACE4(( " (none)" ));
    FT_TRACE4(( "\n\n" ));
#endif

    FT_TRACE4(( "GPOS lookups (style `%s'):\n"
                " ",
                af_style_names[style_class->style] ));

    gpos_lookups = hb_set_create();
    hb_ot_layout_collect_lookups( face,
                                  HB_OT_TAG_GPOS,
                                  script_tags,
                                  NULL,
                                  coverage_tags,
                                  gpos_lookups );

#ifdef FT_DEBUG_LEVEL_TRACE
    count = 0;
#endif

    gpos_glyphs = hb_set_create();
    for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gpos_lookups, &idx ); )
    {
#ifdef FT_DEBUG_LEVEL_TRACE
      FT_TRACE4(( " %d", idx ));
      count++;
#endif

      /* get input coverage of GPOS feature */
      hb_ot_layout_lookup_collect_glyphs( face,
                                          HB_OT_TAG_GPOS,
                                          idx,
                                          NULL,
                                          gpos_glyphs,
                                          NULL,
                                          NULL );
    }

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( !count )
      FT_TRACE4(( " (none)" ));
    FT_TRACE4(( "\n\n" ));
#endif

    /*
     * We now check whether we can construct blue zones, using glyphs
     * covered by the feature only.  In case there is not a single zone
     * (this is, not a single character is covered), we skip this coverage.
     *
     */
    if ( style_class->coverage != AF_COVERAGE_DEFAULT )
    {
      AF_Blue_Stringset         bss = style_class->blue_stringset;
      const AF_Blue_StringRec*  bs  = &af_blue_stringsets[bss];

      FT_Bool  found = 0;

      for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
      {
        const char*  p = &af_blue_strings[bs->string];

        while ( *p )
        {
          hb_codepoint_t  ch;

          GET_UTF8_CHAR( ch, p );

          for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups,
                                                         &idx ); )
          {
            hb_codepoint_t  gidx = FT_Get_Char_Index( globals->face, ch );

            if ( hb_ot_layout_lookup_would_substitute( face, idx,
                                                       &gidx, 1, 1 ) )
            {
              found = 1;
              break;
            }
          }
        }
      }

      if ( !found )
      {
        FT_TRACE4(( "  no blue characters found; style skipped\n" ));
        goto Exit;
      }
    }

    /*
     * Various OpenType features might use the same glyphs at different
     * vertical positions; for example, superscript and subscript glyphs
     * could be the same.  However, the auto-hinter is completely
     * agnostic of OpenType features after the feature analysis has been
     * completed: The engine then simply receives a glyph index and returns a
     * hinted and usually rendered glyph.
     *
     * Consider the superscript feature of font `pala.ttf': Some of the
     * glyphs are `real', this is, they have a zero vertical offset, but
     * most of them are small caps glyphs shifted up to the superscript
     * position (this is, the `sups' feature is present in both the GSUB and
     * GPOS tables).  The code for blue zones computation actually uses a
     * feature's y offset so that the `real' glyphs get correct hints.  But
     * later on it is impossible to decide whether a glyph index belongs to,
     * say, the small caps or superscript feature.
     *
     * For this reason, we don't assign a style to a glyph if the current
     * feature covers the glyph in both the GSUB and the GPOS tables.  This
     * is quite a broad condition, assuming that
     *
     *   (a) glyphs that get used in multiple features are present in a
     *       feature without vertical shift,
     *
     * and
     *
     *   (b) a feature's GPOS data really moves the glyph vertically.
     *
     * Not fulfilling condition (a) makes a font larger; it would also
     * reduce the number of glyphs that could be addressed directly without
     * using OpenType features, so this assumption is rather strong.
     *
     * Condition (b) is much weaker, and there might be glyphs which get
     * missed.  However, the OpenType features we are going to handle are
     * primarily located in GSUB, and HarfBuzz doesn't provide an API to
     * directly get the necessary information from the GPOS table.  A
     * possible solution might be to directly parse the GPOS table to find
     * out whether a glyph gets shifted vertically, but this is something I
     * would like to avoid if not really necessary.
     *
     * Note that we don't follow this logic for the default coverage.
     * Complex scripts like Devanagari have mandatory GPOS features to
     * position many glyph elements, using mark-to-base or mark-to-ligature
     * tables; the number of glyphs missed due to condition (b) would be far
     * too large.
     *
     */
    if ( style_class->coverage != AF_COVERAGE_DEFAULT )
      hb_set_subtract( gsub_glyphs, gpos_glyphs );

#ifdef FT_DEBUG_LEVEL_TRACE
    FT_TRACE4(( "  glyphs without GPOS data (`*' means already assigned)" ));
    count = 0;
#endif

    for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_glyphs, &idx ); )
    {
#ifdef FT_DEBUG_LEVEL_TRACE
      if ( !( count % 10 ) )
        FT_TRACE4(( "\n"
                    "   " ));

      FT_TRACE4(( " %d", idx ));
      count++;
#endif

      /* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */
      /* can be arbitrary: some fonts use fake indices for processing   */
      /* internal to GSUB or GPOS, which is fully valid                 */
      if ( idx >= (hb_codepoint_t)globals->glyph_count )
        continue;

      if ( gstyles[idx] == AF_STYLE_UNASSIGNED )
        gstyles[idx] = (FT_UShort)style_class->style;
#ifdef FT_DEBUG_LEVEL_TRACE
      else
        FT_TRACE4(( "*" ));
#endif
    }

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( !count )
      FT_TRACE4(( "\n"
                  "    (none)" ));
    FT_TRACE4(( "\n\n" ));
#endif

  Exit:
    hb_set_destroy( gsub_lookups );
    hb_set_destroy( gsub_glyphs  );
    hb_set_destroy( gpos_lookups );
    hb_set_destroy( gpos_glyphs  );

    return FT_Err_Ok;
  }

  /* construct HarfBuzz features */
#undef  COVERAGE
#define COVERAGE( name, NAME, description,                \
                  tag1, tag2, tag3, tag4 )                \
          static const hb_feature_t  name ## _feature[] = \
          {                                               \
            {                                             \
              HB_TAG( tag1, tag2, tag3, tag4 ),           \
              1, 0, (unsigned int)-1                      \
            }                                             \
          };

#include "afcover.h"

  /* define mapping between HarfBuzz features and AF_Coverage */
#undef  COVERAGE
#define COVERAGE( name, NAME, description, \
                  tag1, tag2, tag3, tag4 ) \
          name ## _feature,

  static const hb_feature_t*  features[] =
  {
#include "afcover.h"

    NULL /* AF_COVERAGE_DEFAULT */
  };

  void*
  af_shaper_buf_create( FT_Face  face )
  {
    FT_UNUSED( face );

    return (void*)hb_buffer_create();
  }

  void
  af_shaper_buf_destroy( FT_Face  face,
                         void*    buf )
  {
    FT_UNUSED( face );

    hb_buffer_destroy( (hb_buffer_t*)buf );
  }

  const char*
  af_shaper_get_cluster( const char*      p,
                         AF_StyleMetrics  metrics,
                         void*            buf_,
                         unsigned int*    count )
  {
    AF_StyleClass        style_class;
    const hb_feature_t*  feature;
    FT_Int               upem;
    const char*          q;
    int                  len;

    hb_buffer_t*    buf = (hb_buffer_t*)buf_;
    hb_font_t*      font;
    hb_codepoint_t  dummy;

    upem        = (FT_Int)metrics->globals->face->units_per_EM;
    style_class = metrics->style_class;
    feature     = features[style_class->coverage];

    font = metrics->globals->hb_font;

    /* we shape at a size of units per EM; this means font units */
    hb_font_set_scale( font, upem, upem );

    while ( *p == ' ' )
      p++;

    /* count bytes up to next space (or end of buffer) */
    q = p;
    while ( !( *q == ' ' || *q == '\0' ) )
      GET_UTF8_CHAR( dummy, q );
    len = (int)( q - p );

    /* feed character(s) to the HarfBuzz buffer */
    hb_buffer_clear_contents( buf );
    hb_buffer_add_utf8( buf, p, len, 0, len );

    /* we let HarfBuzz guess the script and writing direction */
    hb_buffer_guess_segment_properties( buf );

    /* shape buffer, which means conversion from character codes to */
    /* glyph indices, possibly applying a feature                   */
    hb_shape( font, buf, feature, feature ? 1 : 0 );

    if ( feature )
    {
      hb_buffer_t*  hb_buf = metrics->globals->hb_buf;

      unsigned int      gcount;
      hb_glyph_info_t*  ginfo;

      unsigned int      hb_gcount;
      hb_glyph_info_t*  hb_ginfo;

      /* we have to check whether applying a feature does actually change */
      /* glyph indices; otherwise the affected glyph or glyphs aren't     */
      /* available at all in the feature                                  */

      hb_buffer_clear_contents( hb_buf );
      hb_buffer_add_utf8( hb_buf, p, len, 0, len );
      hb_buffer_guess_segment_properties( hb_buf );
      hb_shape( font, hb_buf, NULL, 0 );

      ginfo    = hb_buffer_get_glyph_infos( buf, &gcount );
      hb_ginfo = hb_buffer_get_glyph_infos( hb_buf, &hb_gcount );

      if ( gcount == hb_gcount )
      {
        unsigned int  i;

        for (i = 0; i < gcount; i++ )
          if ( ginfo[i].codepoint != hb_ginfo[i].codepoint )
            break;

        if ( i == gcount )
        {
          /* both buffers have identical glyph indices */
          hb_buffer_clear_contents( buf );
        }
      }
    }

    *count = hb_buffer_get_length( buf );

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( feature && *count > 1 )
      FT_TRACE1(( "af_shaper_get_cluster:"
                  " input character mapped to multiple glyphs\n" ));
#endif

    return q;
  }

  FT_ULong
  af_shaper_get_elem( AF_StyleMetrics  metrics,
                      void*            buf_,
                      unsigned int     idx,
                      FT_Long*         advance,
                      FT_Long*         y_offset )
  {
    hb_buffer_t*          buf = (hb_buffer_t*)buf_;
    hb_glyph_info_t*      ginfo;
    hb_glyph_position_t*  gpos;
    unsigned int          gcount;

    FT_UNUSED( metrics );

    ginfo = hb_buffer_get_glyph_infos( buf, &gcount );
    gpos  = hb_buffer_get_glyph_positions( buf, &gcount );

    if ( idx >= gcount )
      return 0;

    if ( advance )
      *advance = gpos[idx].x_advance;
    if ( y_offset )
      *y_offset = gpos[idx].y_offset;

    return ginfo[idx].codepoint;
  }

#else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */

  FT_Error
  af_shaper_get_coverage( AF_FaceGlobals  globals,
                          AF_StyleClass   style_class,
                          FT_UShort*      gstyles,
                          FT_Bool         default_script )
  {
    FT_UNUSED( globals );
    FT_UNUSED( style_class );
    FT_UNUSED( gstyles );
    FT_UNUSED( default_script );

    return FT_Err_Ok;
  }

  void*
  af_shaper_buf_create( FT_Face  face )
  {
    FT_UNUSED( face );

    return NULL;
  }

  void
  af_shaper_buf_destroy( FT_Face  face,
                         void*    buf )
  {
    FT_UNUSED( face );
    FT_UNUSED( buf );
  }

  const char*
  af_shaper_get_cluster( const char*      p,
                         AF_StyleMetrics  metrics,
                         void*            buf_,
                         unsigned int*    count )
  {
    FT_Face    face      = metrics->globals->face;
    FT_ULong   ch, dummy = 0;
    FT_ULong*  buf       = (FT_ULong*)buf_;

    while ( *p == ' ' )
      p++;

    GET_UTF8_CHAR( ch, p );

    /* since we don't have an engine to handle clusters, */
    /* we scan the characters but return zero            */
    while ( !( *p == ' ' || *p == '\0' ) )
      GET_UTF8_CHAR( dummy, p );

    if ( dummy )
    {
      *buf   = 0;
      *count = 0;
    }
    else
    {
      *buf   = FT_Get_Char_Index( face, ch );
      *count = 1;
    }

    return p;
  }

  FT_ULong
  af_shaper_get_elem( AF_StyleMetrics  metrics,
                      void*            buf_,
                      unsigned int     idx,
                      FT_Long*         advance,
                      FT_Long*         y_offset )
  {
    FT_Face   face        = metrics->globals->face;
    FT_ULong  glyph_index = *(FT_ULong*)buf_;

    FT_UNUSED( idx );

    if ( advance )
      FT_Get_Advance( face,
                      glyph_index,
                      FT_LOAD_NO_SCALE         |
                      FT_LOAD_NO_HINTING       |
                      FT_LOAD_IGNORE_TRANSFORM,
                      advance );

    if ( y_offset )
      *y_offset = 0;

    return glyph_index;
  }

#endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */

/* END */


text:
/****************************************************************************
 *
 * afindic.c
 *
 *   Auto-fitter hinting routines for Indic writing system (body).
 *
 * Copyright (C) 2007-2020 by
 * Rahul Bhalerao <rahul.bhalerao@redhat.com>, <b.rahul.pm@gmail.com>.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

#include "aftypes.h"
#include "aflatin.h"
#include "afcjk.h"

#ifdef AF_CONFIG_OPTION_INDIC

#include "afindic.h"
#include "aferrors.h"

#ifdef AF_CONFIG_OPTION_USE_WARPER
#include "afwarp.h"
#endif

  static FT_Error
  af_indic_metrics_init( AF_CJKMetrics  metrics,
                         FT_Face        face )
  {
    /* skip blue zone init in CJK routines */
    FT_CharMap  oldmap = face->charmap;

    metrics->units_per_em = face->units_per_EM;

    if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
      face->charmap = NULL;
    else
    {
      af_cjk_metrics_init_widths( metrics, face );
#if 0
      /* either need indic specific blue_chars[] or just skip blue zones */
      af_cjk_metrics_init_blues( metrics, face, af_cjk_blue_chars );
#endif
      af_cjk_metrics_check_digits( metrics, face );
    }

    FT_Set_Charmap( face, oldmap );

    return FT_Err_Ok;
  }

  static void
  af_indic_metrics_scale( AF_CJKMetrics  metrics,
                          AF_Scaler      scaler )
  {
    /* use CJK routines */
    af_cjk_metrics_scale( metrics, scaler );
  }

  static FT_Error
  af_indic_hints_init( AF_GlyphHints  hints,
                       AF_CJKMetrics  metrics )
  {
    /* use CJK routines */
    return af_cjk_hints_init( hints, metrics );
  }

  static FT_Error
  af_indic_hints_apply( FT_UInt        glyph_index,
                        AF_GlyphHints  hints,
                        FT_Outline*    outline,
                        AF_CJKMetrics  metrics )
  {
    /* use CJK routines */
    return af_cjk_hints_apply( glyph_index, hints, outline, metrics );
  }

  /* Extract standard_width from writing system/script specific */
  /* metrics class.                                             */

  static void
  af_indic_get_standard_widths( AF_CJKMetrics  metrics,
                                FT_Pos*        stdHW,
                                FT_Pos*        stdVW )
  {
    if ( stdHW )
      *stdHW = metrics->axis[AF_DIMENSION_VERT].standard_width;

    if ( stdVW )
      *stdVW = metrics->axis[AF_DIMENSION_HORZ].standard_width;
  }

  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                I N D I C   S C R I P T   C L A S S            *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  AF_DEFINE_WRITING_SYSTEM_CLASS(
    af_indic_writing_system_class,

    AF_WRITING_SYSTEM_INDIC,

    sizeof ( AF_CJKMetricsRec ),

    (AF_WritingSystem_InitMetricsFunc) af_indic_metrics_init,        /* style_metrics_init    */
    (AF_WritingSystem_ScaleMetricsFunc)af_indic_metrics_scale,       /* style_metrics_scale   */
    (AF_WritingSystem_DoneMetricsFunc) NULL,                         /* style_metrics_done    */
    (AF_WritingSystem_GetStdWidthsFunc)af_indic_get_standard_widths, /* style_metrics_getstdw */

    (AF_WritingSystem_InitHintsFunc)   af_indic_hints_init,          /* style_hints_init      */
    (AF_WritingSystem_ApplyHintsFunc)  af_indic_hints_apply          /* style_hints_apply     */
  )

#else /* !AF_CONFIG_OPTION_INDIC */

  AF_DEFINE_WRITING_SYSTEM_CLASS(
    af_indic_writing_system_class,

    AF_WRITING_SYSTEM_INDIC,

    sizeof ( AF_CJKMetricsRec ),

    (AF_WritingSystem_InitMetricsFunc) NULL, /* style_metrics_init    */
    (AF_WritingSystem_ScaleMetricsFunc)NULL, /* style_metrics_scale   */
    (AF_WritingSystem_DoneMetricsFunc) NULL, /* style_metrics_done    */
    (AF_WritingSystem_GetStdWidthsFunc)NULL, /* style_metrics_getstdw */

    (AF_WritingSystem_InitHintsFunc)   NULL, /* style_hints_init      */
    (AF_WritingSystem_ApplyHintsFunc)  NULL  /* style_hints_apply     */
  )

#endif /* !AF_CONFIG_OPTION_INDIC */

/* END */


text:
/****************************************************************************
 *
 * aferrors.h
 *
 *   Autofitter error codes (specification only).
 *
 * Copyright (C) 2005-2020 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */

  /**************************************************************************
   *
   * This file is used to define the Autofitter error enumeration
   * constants.
   *
   */

#ifndef AFERRORS_H_
#define AFERRORS_H_

#include FT_MODULE_ERRORS_H

#undef FTERRORS_H_

#undef  FT_ERR_PREFIX
#define FT_ERR_PREFIX  AF_Err_
#define FT_ERR_BASE    FT_Mod_Err_Autofit

#include FT_ERRORS_H

#endif /* AFERRORS_H_ */

/* END */