/****************************************************************************
 *
 * afshaper.c
 *
 *   HarfBuzz interface for accessing OpenType features (body).
 *
 * Copyright (C) 2013-2025 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 <freetype/freetype.h>
#include <freetype/ftadvanc.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,


  FT_LOCAL_ARRAY_DEF( hb_script_t )
  af_hb_scripts[] =
  {
#include "afscript.h"
  };


  static FT_Error
  af_shaper_get_coverage_hb( 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        = af_hb_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.                                    */
    {
      unsigned int  tags_count = 3;
      hb_tag_t      tags[3];


      hb( ot_tags_from_script_and_language )( script,
                                              HB_LANGUAGE_INVALID,
                                              &tags_count,
                                              tags,
                                              NULL,
                                              NULL );
      script_tags[0] = tags_count > 0 ? tags[0] : HB_TAG_NONE;
      script_tags[1] = tags_count > 1 ? tags[1] : HB_TAG_NONE;
      script_tags[2] = tags_count > 2 ? tags[2] : HB_TAG_NONE;
    }

    /* If the second tag is HB_OT_TAG_DEFAULT_SCRIPT, 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;
    }

    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] ));
    FT_TRACE4(( " " ));

#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(( " %u", 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" ));
    FT_TRACE4(( "\n" ));
#endif

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

    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(( " %u", 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" ));
    FT_TRACE4(( "\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
     * (that 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', that is, they have a zero vertical offset, but
     * most of them are small caps glyphs shifted up to the superscript
     * position (that 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(( "   " ));
      }

      FT_TRACE4(( " %u", 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" ));
      FT_TRACE4(( "    (none)" ));
    }
    FT_TRACE4(( "\n" ));
    FT_TRACE4(( "\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 */
  };


  static void*
  af_shaper_buf_create_hb( AF_FaceGlobals  globals )
  {
    FT_UNUSED( globals );

    return (void*)hb( buffer_create )();
  }


  static void
  af_shaper_buf_destroy_hb( AF_FaceGlobals  globals,
                            void*           buf )
  {
    FT_UNUSED( globals );

    hb( buffer_destroy )( (hb_buffer_t*)buf );
  }


  static const char*
  af_shaper_get_cluster_hb( const char*      p,
                            AF_StyleMetrics  metrics,
                            void*            buf_,
                            unsigned int*    count )
  {
    AF_FaceGlobals  globals = metrics->globals;

    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;

    FT_UNUSED( globals );


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


  static FT_ULong
  af_shaper_get_elem_hb( AF_StyleMetrics  metrics,
                         void*            buf_,
                         unsigned int     idx,
                         FT_Long*         advance,
                         FT_Long*         y_offset )
  {
    AF_FaceGlobals  globals = metrics->globals;

    hb_buffer_t*          buf = (hb_buffer_t*)buf_;
    hb_glyph_info_t*      ginfo;
    hb_glyph_position_t*  gpos;
    unsigned int          gcount;

    FT_UNUSED( globals );


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


#endif /* FT_CONFIG_OPTION_USE_HARFBUZZ */


  static FT_Error
  af_shaper_get_coverage_nohb( 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;
  }


  static void*
  af_shaper_buf_create_nohb( AF_FaceGlobals  globals )
  {
    FT_UNUSED( globals );

    return NULL;
  }


  static void
  af_shaper_buf_destroy_nohb( AF_FaceGlobals  globals,
                              void*    buf )
  {
    FT_UNUSED( globals );
    FT_UNUSED( buf );
  }


  static const char*
  af_shaper_get_cluster_nohb( 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;
  }


  static FT_ULong
  af_shaper_get_elem_nohb( 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;
  }


  /********************************************************************/

  FT_Error
  af_shaper_get_coverage( AF_FaceGlobals  globals,
                          AF_StyleClass   style_class,
                          FT_UShort*      gstyles,
                          FT_Bool         default_script )
  {
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
    if ( ft_hb_enabled( globals ) )
      return af_shaper_get_coverage_hb( globals,
                                        style_class,
                                        gstyles,
                                        default_script );
    else
#endif
      return af_shaper_get_coverage_nohb( globals,
                                          style_class,
                                          gstyles,
                                          default_script );
  }


  void*
  af_shaper_buf_create( AF_FaceGlobals  globals )
  {
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
    if ( ft_hb_enabled( globals ) )
      return af_shaper_buf_create_hb( globals );
    else
#endif
      return af_shaper_buf_create_nohb( globals );
  }


  void
  af_shaper_buf_destroy( AF_FaceGlobals  globals,
                         void*           buf )
  {
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
    if ( ft_hb_enabled( globals ) )
      af_shaper_buf_destroy_hb( globals, buf );
    else
#endif
      af_shaper_buf_destroy_nohb( globals, buf );
  }


  const char*
  af_shaper_get_cluster( const char*      p,
                         AF_StyleMetrics  metrics,
                         void*            buf_,
                         unsigned int*    count )
  {
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
    if ( ft_hb_enabled( metrics->globals ) )
      return af_shaper_get_cluster_hb( p, metrics, buf_, count );
    else
#endif
      return af_shaper_get_cluster_nohb( p, metrics, buf_, count );
  }


  FT_ULong
  af_shaper_get_elem( AF_StyleMetrics  metrics,
                      void*            buf_,
                      unsigned int     idx,
                      FT_Long*         advance,
                      FT_Long*         y_offset )
  {
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
    if ( ft_hb_enabled( metrics->globals ) )
      return af_shaper_get_elem_hb( metrics,
                                    buf_,
                                    idx,
                                    advance,
                                    y_offset );
#endif
      return af_shaper_get_elem_nohb( metrics,
                                      buf_,
                                      idx,
                                      advance,
                                      y_offset );
  }


/* END */
