/****************************************************************************
 *
 * afgsub.c
 *
 *   Auto-fitter routines to parse the GSUB table (body).
 *
 * Copyright (C) 2025-2026 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/tttables.h>
#include <freetype/tttags.h>

#include <freetype/internal/ftstream.h>


#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ

#include "afglobal.h"
#include "afgsub.h"
#include "aftypes.h"


  /*********************************/
  /********                 ********/
  /******** GSUB validation ********/
  /********                 ********/
  /*********************************/


  static FT_Bool
  af_validate_coverage( FT_Byte*  table,
                        FT_Byte*  table_limit,
                        FT_UInt  *num_glyphs )
  {
    FT_UInt  format;

    FT_Byte*  p     = table;
    FT_UInt   count = 0;


    if ( table_limit < p + 4 )
      return FALSE;

    format = FT_NEXT_USHORT( p );
    if ( format == 1 )
    {
      FT_UInt  glyphCount = FT_NEXT_USHORT( p );


      /* We don't validate glyph IDs. */
      if ( table_limit < p + glyphCount * 2 )
        return FALSE;

      count += glyphCount;
    }
    else if ( format == 2 )
    {
      FT_UInt   rangeCount = FT_NEXT_USHORT( p );
      FT_Byte*  limit      = p + rangeCount * 6;
      FT_UInt   startGlyphID;
      FT_UInt   endGlyphID = 0;


      if ( table_limit < limit )
        return FALSE;

      while ( p < limit )
      {
        startGlyphID = FT_NEXT_USHORT( p );
        if ( startGlyphID < endGlyphID )
          return FALSE;

        endGlyphID = FT_NEXT_USHORT( p );
        if ( endGlyphID < startGlyphID )
          return FALSE;

        count += ++endGlyphID - startGlyphID;

        /* We don't validate coverage indices. */
        p += 2;
      }
    }
    else
      return FALSE;

    if ( num_glyphs )
      *num_glyphs = count;

    return TRUE;
  }


  static FT_Bool
  af_validate_single_subst1( FT_Byte*  table,
                             FT_Byte*  table_limit )
  {
    FT_Byte*  coverage;


    /* Subtable format is already checked. */

    /* The four bytes for the coverage table offset */
    /* and the glyph ID delta are already checked.  */
    coverage = table + FT_PEEK_USHORT( table + 2 );
    if ( !af_validate_coverage( coverage, table_limit, NULL ) )
      return FALSE;

    /* We don't validate glyph IDs. */

    return TRUE;
  }


  static FT_Bool
  af_validate_single_subst2( FT_Byte*  table,
                             FT_Byte*  table_limit )
  {
    FT_Byte*  coverage;
    FT_UInt   glyphCount;
    FT_UInt   num_glyphs;

    /* Subtable format is already checked. */
    FT_Byte*  p = table + 2;


    /* The four bytes for the coverage table offset */
    /* and `glyphCount` are already checked.        */
    coverage = table + FT_NEXT_USHORT( p );
    if ( !af_validate_coverage( coverage, table_limit, &num_glyphs ) )
      return FALSE;

    glyphCount = FT_NEXT_USHORT( p );
    /* We don't validate glyph IDs. */
    if ( table_limit < p + glyphCount * 2 )
      return FALSE;

    if ( glyphCount != num_glyphs )
      return FALSE;

    return TRUE;
  }


  static FT_Bool
  af_validate_alternate( FT_Byte*  table,
                         FT_Byte*  table_limit )
  {
    FT_Byte*  coverage;
    FT_UInt   alternateSetCount;
    FT_UInt   num_glyphs;

    /* Subtable format is already checked. */
    FT_Byte*  p = table + 2;
    FT_Byte*  limit;


    /* The four bytes for the coverage table offset */
    /* and `alternateSetCount` are already checked. */
    coverage = table + FT_NEXT_USHORT( p );
    if ( !af_validate_coverage( coverage, table_limit, &num_glyphs ) )
      return FALSE;

    alternateSetCount = FT_NEXT_USHORT( p );
    limit             = p + alternateSetCount * 2;
    if ( table_limit < limit )
      return FALSE;

    if ( alternateSetCount != num_glyphs )
      return FALSE;

    while ( p < limit )
    {
      FT_Byte*  alternate_set;
      FT_UInt   glyphCount;


      alternate_set = table + FT_NEXT_USHORT( p );
      if ( table_limit < alternate_set + 2 )
        return FALSE;

      glyphCount = FT_PEEK_USHORT( alternate_set );
      /* We don't validate glyph IDs. */
      if ( table_limit < alternate_set + 2 + glyphCount * 2 )
        return FALSE;
    }

    return TRUE;
  }


  /* Validate 'SingleSubst' and 'AlternateSubst' lookup tables. */
  static FT_Bool
  af_validate_lookup_table( FT_Byte*  table,
                            FT_Byte*  table_limit )
  {
    FT_UInt  lookupType;
    FT_UInt  real_lookupType = 0;
    FT_UInt  subtableCount;

    FT_Byte*  p = table;
    FT_Byte*  limit;


    if ( table_limit < p + 6 )
      return FALSE;

    lookupType = FT_NEXT_USHORT( p );

    p += 2; /* Skip `lookupFlag`. */

    subtableCount = FT_NEXT_USHORT( p );
    limit         = p + subtableCount * 2;
    if ( table_limit < limit )
      return FALSE;

    while ( p < limit )
    {
      FT_Byte*  subtable = table + FT_NEXT_USHORT( p );
      FT_UInt   format;


      if ( lookupType == 7 )
      {
        /* Substitution extension. */
        FT_Byte*  q = subtable;


        if ( table_limit < q + 8 )
          return FALSE;

        if ( FT_NEXT_USHORT( q ) != 1 ) /* format */
          return FALSE;

        if ( real_lookupType == 0 )
          real_lookupType = FT_NEXT_USHORT( q );
        else if ( real_lookupType != FT_NEXT_USHORT( q ) )
          return FALSE;

        subtable += FT_PEEK_ULONG( q );
      }
      else
        real_lookupType = lookupType;

      /* Ensure the first six bytes of all subtable formats. */
      if ( table_limit < subtable + 6 )
        return FALSE;

      format = FT_PEEK_USHORT( subtable );

      if ( real_lookupType == 1 )
      {
        if ( format == 1 )
        {
          if ( !af_validate_single_subst1( subtable, table_limit ) )
            return FALSE;
        }
        else if ( format == 2 )
        {
          if ( !af_validate_single_subst2( subtable, table_limit ) )
            return FALSE;
        }
        else
          return FALSE;
      }
      else if ( real_lookupType == 3 )
      {
        if ( format == 1 )
        {
          if ( !af_validate_alternate( subtable, table_limit ) )
            return FALSE;
        }
        else
          return FALSE;
      }
      else
        return FALSE;
    }

    return TRUE;
  }


  FT_LOCAL_DEF( void )
  af_parse_gsub( AF_FaceGlobals  globals )
  {
    FT_Error  error = FT_Err_Ok;

    FT_Face    face   = globals->face;
    FT_Memory  memory = face->memory;

    FT_ULong  gsub_length;
    FT_Byte*  gsub;
    FT_Byte*  gsub_limit;

    FT_UInt32*  gsub_lookups_single_alternate;

    FT_UInt   lookupListOffset;
    FT_Byte*  lookup_list;
    FT_UInt   lookupCount;

    FT_UInt  idx;

    FT_Byte*  p;
    FT_Byte*  limit;


    globals->gsub                          = NULL;
    globals->gsub_lookups_single_alternate = NULL;

    /* No error if we can't load or parse GSUB data. */

    gsub                          = NULL;
    gsub_lookups_single_alternate = NULL;

    gsub_length = 0;
    if ( FT_Load_Sfnt_Table( face, TTAG_GSUB, 0, NULL, &gsub_length ) )
      goto Fail;

    if ( FT_QALLOC( gsub, gsub_length ) )
      goto Fail;

    if ( FT_Load_Sfnt_Table( face, TTAG_GSUB, 0, gsub, &gsub_length ) )
      goto Fail;

    if ( gsub_length < 10 )
      goto Fail;

    lookupListOffset = FT_PEEK_USHORT( gsub + 8 );
    if ( gsub_length < lookupListOffset + 2 )
      goto Fail;

    lookupCount = FT_PEEK_USHORT( gsub + lookupListOffset );
    if ( gsub_length < lookupListOffset + 2 + lookupCount * 2 )
      goto Fail;

    if ( FT_NEW_ARRAY( gsub_lookups_single_alternate, lookupCount ) )
      goto Fail;

    gsub_limit  = gsub + gsub_length;
    lookup_list = gsub + lookupListOffset;
    p           = lookup_list + 2;
    limit       = p + lookupCount * 2;
    idx         = 0;
    while ( p < limit )
    {
      FT_UInt  lookupOffset = FT_NEXT_USHORT( p );


      if ( af_validate_lookup_table( lookup_list + lookupOffset,
                                     gsub_limit ) )
      {
        /* We store offsets relative to the start of the GSUB table. */
        gsub_lookups_single_alternate[idx] = lookupListOffset + lookupOffset;
      }

      idx++;
    }

    globals->gsub                          = gsub;
    globals->gsub_lookup_count             = lookupCount;
    globals->gsub_lookups_single_alternate = gsub_lookups_single_alternate;

    return;

  Fail:
    globals->gsub_lookup_count = 0;
    FT_FREE( gsub );
    FT_FREE( gsub_lookups_single_alternate );
  }


  /*********************************/
  /********                 ********/
  /********   GSUB access   ********/
  /********                 ********/
  /*********************************/


  static FT_UInt
  af_coverage_format( FT_Byte*  coverage )
  {
    return FT_PEEK_USHORT( coverage );
  }


  static FT_Byte*
  af_coverage_start( FT_Byte*  coverage )
  {
    return coverage + 4;
  }


  static FT_Byte*
  af_coverage_limit( FT_Byte*  coverage )
  {
    if ( af_coverage_format( coverage ) == 1 )
    {
      FT_UInt  glyphCount = FT_PEEK_USHORT( coverage + 2 );


      return af_coverage_start( coverage ) + glyphCount * 2;
    }
    else
    {
      FT_UInt  rangeCount = FT_PEEK_USHORT( coverage + 2 );


      return af_coverage_start( coverage ) + rangeCount * 6;
    }
  }


  typedef struct AF_CoverageIteratorRec_*  AF_CoverageIterator;

  typedef struct  AF_CoverageIteratorRec_
  {
    FT_UInt  format;

    FT_Byte*  p;
    FT_Byte*  limit;

    FT_UInt16  glyph;
    FT_UInt16  glyph_limit;

  } AF_CoverageIteratorRec;


  static FT_Bool
  af_coverage_iterator( AF_CoverageIterator  iter,
                        FT_UInt16*           glyph )
  {
    if ( iter->p >= iter->limit )
      return FALSE;

    if ( iter->format == 1 )
      *glyph = FT_NEXT_USHORT( iter->p );
    else
    {
      if ( iter->glyph > iter->glyph_limit )
      {
        iter->glyph       = FT_NEXT_USHORT( iter->p );
        iter->glyph_limit = FT_NEXT_USHORT( iter->p );

        iter->p += 2;
      }

      *glyph = iter->glyph++;
    }

    return TRUE;
  }


  static AF_CoverageIteratorRec
  af_coverage_iterator_init( FT_Byte*  coverage )
  {
    AF_CoverageIteratorRec  iterator;


    iterator.format      = af_coverage_format( coverage );
    iterator.p           = af_coverage_start( coverage );
    iterator.limit       = af_coverage_limit( coverage );
    iterator.glyph       = 1;
    iterator.glyph_limit = 0;

    return iterator;
  }


  /*
    Because we merge all single and alternate substitution mappings into
    one, large hash, we need the possibility to have multiple glyphs as
    values.  We utilize that we have 32bit integers but only 16bit glyph
    indices, using the following scheme.

    If glyph G maps to a single substitute S, the entry in the map is

      G  ->  S

    If glyph G maps to multiple substitutes S1, S2, ..., Sn, we do

      G                    ->  S1 + ((n - 1) << 16)
      G + (1 << 16)        ->  S2
      G + (2 << 16)        ->  S3
      ...
      G + ((n - 1) << 16)  ->  Sn
  */
  static FT_Error
  af_hash_insert( FT_UInt16  glyph,
                  FT_UInt16  substitute,
                  FT_Hash    map,
                  FT_Memory  memory )
  {
    FT_Error  error;

    size_t*  value = ft_hash_num_lookup( glyph, map );


    if ( !value )
    {
      error = ft_hash_num_insert( glyph, substitute, map, memory );
      if ( error )
        return error;
    }
    else
    {
      /* Get number of substitutes, increased by one... */
      FT_UInt  mask = ( (FT_UInt)*value & 0xFFFF0000U ) + 0x10000U;


      /* ... which becomes the new key mask. */
      error = ft_hash_num_insert( (FT_Int)( glyph | mask ),
                                  substitute,
                                  map,
                                  memory );
      if ( error )
        return error;

      /* Update number of substitutes. */
      *value += 0x10000U;
    }

    return FT_Err_Ok;
  }


  static FT_Error
  af_map_single_subst1( FT_Hash    map,
                        FT_Byte*   table,
                        FT_Memory  memory )
  {
    FT_Error  error;

    FT_Byte*  coverage     = table + FT_PEEK_USHORT( table + 2 );
    FT_UInt   deltaGlyphID = FT_PEEK_USHORT( table + 4 );

    AF_CoverageIteratorRec  iterator = af_coverage_iterator_init( coverage );

    FT_UInt16  glyph;


    while ( af_coverage_iterator( &iterator, &glyph ) )
    {
      /* `deltaGlyphID` requires modulo 65536 arithmetic. */
      FT_UInt16  subst = (FT_UInt16)( ( glyph + deltaGlyphID ) % 0x10000U );


      error = af_hash_insert( glyph, subst, map, memory );
      if ( error )
        return error;
    }

    return FT_Err_Ok;
  }


  static FT_Error
  af_map_single_subst2( FT_Hash    map,
                        FT_Byte*   table,
                        FT_Memory  memory )
  {
    FT_Error  error;

    FT_Byte*  coverage = table + FT_PEEK_USHORT( table + 2 );

    AF_CoverageIteratorRec  iterator = af_coverage_iterator_init( coverage );

    FT_UInt16  glyph;
    FT_Byte*   p = table + 6;


    while ( af_coverage_iterator( &iterator, &glyph ) )
    {
      FT_UInt16  subst = FT_NEXT_USHORT( p );


      error = af_hash_insert( glyph, subst, map, memory );
      if ( error )
        return error;
    }

    return FT_Err_Ok;
  }


  static FT_Error
  af_map_alternate( FT_Hash    map,
                    FT_Byte*   table,
                    FT_Memory  memory )
  {
    FT_Error  error;

    FT_Byte*  coverage = table + FT_PEEK_USHORT( table + 2 );

    AF_CoverageIteratorRec  iterator = af_coverage_iterator_init( coverage );

    FT_UInt16  glyph;
    FT_Byte*   p = table + 6;


    while ( af_coverage_iterator( &iterator, &glyph ) )
    {
      FT_Byte*  alternate_set = table + FT_NEXT_USHORT( p );

      FT_Byte*  q          = alternate_set;
      FT_UInt   glyphCount = FT_NEXT_USHORT( q );

      FT_UInt  i;


      for ( i = 0; i < glyphCount; i++ )
      {
        FT_UInt16  subst = FT_NEXT_USHORT( q );


        error = af_hash_insert( glyph, subst, map, memory );
        if ( error )
          return error;
      }
    }

    return FT_Err_Ok;
  }


  /* Map 'SingleSubst' and 'AlternateSubst' lookup tables. */
  FT_LOCAL_DEF( FT_Error )
  af_map_lookup( AF_FaceGlobals  globals,
                 FT_Hash         map,
                 FT_UInt32       lookup_offset )
  {
    FT_Face    face   = globals->face;
    FT_Memory  memory = face->memory;

    FT_Byte*  table = globals->gsub + lookup_offset;

    FT_UInt  lookupType    = FT_PEEK_USHORT( table );
    FT_UInt  subtableCount = FT_PEEK_USHORT( table + 4 );

    FT_Byte*  p     = table + 6;
    FT_Byte*  limit = p + subtableCount * 2;


    while ( p < limit )
    {
      FT_Error  error;

      FT_UInt  real_lookupType = lookupType;

      FT_Byte*  subtable = table + FT_NEXT_USHORT( p );


      if ( lookupType == 7 )
      {
        FT_Byte*  q = subtable + 2;


        real_lookupType = FT_NEXT_USHORT( q );
        subtable       += FT_PEEK_ULONG( q );
      }

      if ( real_lookupType == 1 )
      {
        FT_UInt  format = FT_PEEK_USHORT( subtable );


        error = ( format == 1 )
                  ? af_map_single_subst1( map, subtable, memory )
                  : af_map_single_subst2( map, subtable, memory );
      }
      else
        error = af_map_alternate( map, subtable, memory );

      if ( error )
        return error;
    }

    return FT_Err_Ok;
  }


#else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */

/* ANSI C doesn't like empty source files */
typedef int  afgsub_dummy_;

#endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */

/* END */
