ftxgsub.c   [plain text]


/*******************************************************************
 *
 *  ftxgsub.c
 *
 *    TrueType Open GSUB table support.
 *
 *  Copyright 1996-1999 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.
 *
 ******************************************************************/

/* XXX There is *a lot* of duplicated code (cf. formats 5 and 6), but
       I don't care currently.  I believe that it would be possible to
       save about 50% of TTO code by carefully designing the structures,
       sharing as much as possible with extensive use of macros.  This
       is something for a volunteer :-)                                  */

#include "tttypes.h"
#include "tttags.h"
#include "ttload.h"
#include "ttextend.h"
#include "ttmemory.h"
#include "ttfile.h"

#include "ftxopen.h"
#include "ftxopenf.h"


#define GSUB_ID  Build_Extension_ID( 'G', 'S', 'U', 'B' )


#define ADD_String( in, num_in, out, num_out, data )                \
          ( ( error = TT_GSUB_Add_String( (in), (num_in),           \
                                          (out), (num_out),         \
                                          (data) ) ) != TT_Err_Ok )

#define CHECK_Property( gdef, index, flags, property )              \
          ( ( error = Check_Property( (gdef), (index), (flags),     \
                                      (property) ) ) != TT_Err_Ok )


  static TT_Error  Do_Glyph_Lookup( TTO_GSUBHeader*   gsub,
                                    UShort            lookup_index,
                                    TTO_GSUB_String*  in,
                                    TTO_GSUB_String*  out,
                                    UShort            context_length,
                                    int               nesting_level );



  /**********************
   * Auxiliary functions
   **********************/


  /* The following function copies `num_out' elements from `data' to
     `out', advancing the array pointer in the `in' structure by `num_in'
     elements and in `out' by `num_out' elements.  If the string (resp.
     the properties) array in `out' is empty or too small, it allocates
     resp. reallocates the string (and properties) array.  Finally, it
     sets the `length' field of `out' equal to `pos' of the `out'
     structure.

     The properties (if defined) for all replaced glyphs are taken from
     the glyph at position `in->pos'.                                     */

  EXPORT_FUNC
  TT_Error  TT_GSUB_Add_String( TTO_GSUB_String*  in,
                                UShort            num_in,
                                TTO_GSUB_String*  out,
                                UShort            num_out,
                                UShort*           data )
  {
    TT_Error  error;
    UShort    i;
    UShort    p_in;
    UShort*   p_out;


    /* sanity check */

    if ( !in || !out ||
         in->length == 0 || in->pos >= in->length ||
         in->length < in->pos + num_in )
      return TT_Err_Invalid_Argument;

    if ( out->pos + num_out >= out->allocated )
    {
      ULong  size = out->pos + num_out + 256L;


      /* The following works because all fields in `out' must be
         initialized to zero (including the `string' field) for the
         first use.                                               */

      if ( REALLOC( out->string, size * sizeof ( UShort ) ) )
        return error;
      if ( in->properties )
        if ( REALLOC( out->properties, size * sizeof ( UShort ) ) )
          return error;
      out->allocated = size;
    }

    if ( num_out )
    {
      MEM_Copy( &out->string[out->pos], data, num_out * sizeof ( UShort ) );
      if ( in->properties )
      {
        p_in  = in->properties[in->pos];
        p_out = out->properties;

        for ( i = out->pos; i < out->pos + num_out; i++ )
          p_out[i] = p_in;
      }
    }

    in->pos  += num_in;
    out->pos += num_out;

    out->length = out->pos;

    return TT_Err_Ok;
  }


  static TT_Error  Check_Property( TTO_GDEFHeader*  gdef,
                                   UShort           index,
                                   UShort           flags,
                                   UShort*          property )
  {
    TT_Error  error;


    if ( gdef )
    {
      error = TT_GDEF_Get_Glyph_Property( gdef, index, property );
      if ( error )
        return error;

      /* This is OpenType 1.2 */

      if ( flags & IGNORE_SPECIAL_MARKS )
        if ( (flags & 0xFF00) != *property )
          return TTO_Err_Not_Covered;

      if ( flags & *property )
        return TTO_Err_Not_Covered;
    }

    return TT_Err_Ok;
  }



  /**********************
   * Extension Functions
   **********************/


  static TT_Error  GSUB_Create( void*  ext,
                                PFace  face )
  {
    DEFINE_LOAD_LOCALS( face->stream );

    TTO_GSUBHeader*  gsub = (TTO_GSUBHeader*)ext;
    Long             table;


    /* by convention */

    if ( !gsub )
      return TT_Err_Ok;

    /* a null offset indicates that there is no GSUB table */

    gsub->offset = 0;

    /* we store the start offset and the size of the subtable */

    table = TT_LookUp_Table( face, TTAG_GSUB );
    if ( table < 0 )
      return TT_Err_Ok;             /* The table is optional */

    if ( FILE_Seek( face->dirTables[table].Offset ) ||
         ACCESS_Frame( 4L ) )
      return error;

    gsub->offset  = FILE_Pos() - 4L;    /* undo ACCESS_Frame() */
    gsub->Version = GET_ULong();

    FORGET_Frame();

    gsub->loaded = FALSE;

    return TT_Err_Ok;
  }


  static TT_Error  GSUB_Destroy( void*  ext,
                                 PFace  face )
  {
    TTO_GSUBHeader*  gsub = (TTO_GSUBHeader*)ext;


    /* by convention */

    if ( !gsub )
      return TT_Err_Ok;

    if ( gsub->loaded )
    {
      Free_LookupList( &gsub->LookupList, GSUB );
      Free_FeatureList( &gsub->FeatureList );
      Free_ScriptList( &gsub->ScriptList );
    }

    return TT_Err_Ok;
  }


  EXPORT_FUNC
  TT_Error  TT_Init_GSUB_Extension( TT_Engine  engine )
  {
    PEngine_Instance  _engine = HANDLE_Engine( engine );


    if ( !_engine )
      return TT_Err_Invalid_Engine;

    return  TT_Register_Extension( _engine,
                                   GSUB_ID,
                                   sizeof ( TTO_GSUBHeader ),
                                   GSUB_Create,
                                   GSUB_Destroy );
  }


  EXPORT_FUNC
  TT_Error  TT_Load_GSUB_Table( TT_Face          face,
                                TTO_GSUBHeader*  retptr,
                                TTO_GDEFHeader*  gdef )
  {
    ULong            cur_offset, new_offset, base_offset;

    TT_UShort        i, num_lookups;
    TT_Error         error;
    TT_Stream        stream;
    TTO_GSUBHeader*  gsub;
    TTO_Lookup*      lo;

    PFace  faze = HANDLE_Face( face );


    if ( !retptr )
      return TT_Err_Invalid_Argument;

    if ( !faze )
      return TT_Err_Invalid_Face_Handle;

    error = TT_Extension_Get( faze, GSUB_ID, (void**)&gsub );
    if ( error )
      return error;

    if ( gsub->offset == 0 )
      return TT_Err_Table_Missing;      /* no GSUB table; nothing to do */

    /* now access stream */

    if ( USE_Stream( faze->stream, stream ) )
      return error;

    base_offset = gsub->offset;

    /* skip version */

    if ( FILE_Seek( base_offset + 4L ) ||
         ACCESS_Frame( 2L ) )
      return error;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_ScriptList( &gsub->ScriptList,
                                    faze ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail3;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_FeatureList( &gsub->FeatureList,
                                     faze ) ) != TT_Err_Ok )
      goto Fail3;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_LookupList( &gsub->LookupList,
                                    faze, GSUB ) ) != TT_Err_Ok )
      goto Fail2;

    gsub->gdef = gdef;      /* can be NULL */

    /* We now check the LookupFlags for values larger than 0xFF to find
       out whether we need to load the `MarkAttachClassDef' field of the
       GDEF table -- this hack is necessary for OpenType 1.2 tables since
       the version field of the GDEF table hasn't been incremented.

       For constructed GDEF tables, we only load it if
       `MarkAttachClassDef_offset' is not zero (nevertheless, a build of
       a constructed mark attach table is not supported currently).       */

    if ( gdef &&
         gdef->MarkAttachClassDef_offset && !gdef->MarkAttachClassDef.loaded )
    {
      lo          = gsub->LookupList.Lookup;
      num_lookups = gsub->LookupList.LookupCount;

      for ( i = 0; i < num_lookups; i++ )
      {
        if ( lo[i].LookupFlag & IGNORE_SPECIAL_MARKS )
        {
          if ( FILE_Seek( gdef->MarkAttachClassDef_offset ) ||
               ACCESS_Frame( 2L ) )
            goto Fail1;

          new_offset = GET_UShort();

          FORGET_Frame();

          if ( !new_offset )
            return TTO_Err_Invalid_GDEF_SubTable;

          new_offset += base_offset;

          if ( FILE_Seek( new_offset ) ||
               ( error = Load_ClassDefinition( &gdef->MarkAttachClassDef,
                                               256, faze ) ) != TT_Err_Ok )
            goto Fail1;

          break;
        }
      }
    }

    gsub->loaded = TRUE;
    *retptr = *gsub;
    DONE_Stream( stream );

    return TT_Err_Ok;

  Fail1:
    Free_LookupList( &gsub->LookupList, GSUB );

  Fail2:
    Free_FeatureList( &gsub->FeatureList );

  Fail3:
    Free_ScriptList( &gsub->ScriptList );

    /* release stream */

    DONE_Stream( stream );

    return error;
  }



  /*****************************
   * SubTable related functions
   *****************************/


  /* LookupType 1 */

  /* SingleSubstFormat1 */
  /* SingleSubstFormat2 */

  TT_Error  Load_SingleSubst( TTO_SingleSubst*  ss,
                              PFace             input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort   n, count;
    ULong    cur_offset, new_offset, base_offset;

    UShort*  s;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 4L ) )
      return error;

    ss->SubstFormat = GET_UShort();
    new_offset      = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &ss->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    switch ( ss->SubstFormat )
    {
    case 1:
      if ( ACCESS_Frame( 2L ) )
        goto Fail2;

      ss->ssf.ssf1.DeltaGlyphID = GET_UShort();

      FORGET_Frame();

      break;

    case 2:
      if ( ACCESS_Frame( 2L ) )
        goto Fail2;

      count = ss->ssf.ssf2.GlyphCount = GET_UShort();

      FORGET_Frame();

      ss->ssf.ssf2.Substitute = NULL;

      if ( ALLOC_ARRAY( ss->ssf.ssf2.Substitute, count, UShort ) )
        goto Fail2;

      s = ss->ssf.ssf2.Substitute;

      if ( ACCESS_Frame( count * 2L ) )
        goto Fail1;

      for ( n = 0; n < count; n++ )
        s[n] = GET_UShort();

      FORGET_Frame();

      break;

    default:
      return TTO_Err_Invalid_GSUB_SubTable_Format;
    }

    return TT_Err_Ok;

  Fail1:
    FREE( s );

  Fail2:
    Free_Coverage( &ss->Coverage );
    return error;
  }


  void  Free_SingleSubst( TTO_SingleSubst*  ss )
  {
    switch ( ss->SubstFormat )
    {
    case 1:
      break;

    case 2:
      FREE( ss->ssf.ssf2.Substitute );
      break;
    }

    Free_Coverage( &ss->Coverage );
  }


  static TT_Error  Lookup_SingleSubst( TTO_SingleSubst*  ss,
                                       TTO_GSUB_String*  in,
                                       TTO_GSUB_String*  out,
                                       UShort            flags,
                                       UShort            context_length,
                                       TTO_GDEFHeader*   gdef )
  {
    UShort    index, value[1], property;
    TT_Error  error;


    if ( context_length != 0xFFFF && context_length < 1 )
      return TTO_Err_Not_Covered;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    error = Coverage_Index( &ss->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    switch ( ss->SubstFormat )
    {
    case 1:
      value[0] = ( in->string[in->pos] + ss->ssf.ssf1.DeltaGlyphID ) & 0xFFFF;
      if ( ADD_String( in, 1, out, 1, value ) )
        return error;
      break;

    case 2:
      if ( index >= ss->ssf.ssf2.GlyphCount )
        return TTO_Err_Invalid_GSUB_SubTable;
      value[0] = ss->ssf.ssf2.Substitute[index];
      if ( ADD_String( in, 1, out, 1, value ) )
        return error;
      break;

    default:
      return TTO_Err_Invalid_GSUB_SubTable;
    }

    if ( gdef && gdef->NewGlyphClasses )
    {
      /* we inherit the old glyph class to the substituted glyph */

      error = Add_Glyph_Property( gdef, value[0], property );
      if ( error && error != TTO_Err_Not_Covered )
        return error;
    }

    return TT_Err_Ok;
  }


  /* LookupType 2 */

  /* Sequence */

  static TT_Error  Load_Sequence( TTO_Sequence*  s,
                                  PFace          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort   n, count;
    UShort*  sub;


    if ( ACCESS_Frame( 2L ) )
      return error;

    count = s->GlyphCount = GET_UShort();

    FORGET_Frame();

    s->Substitute = NULL;

    if ( count )
    {
      if ( ALLOC_ARRAY( s->Substitute, count, UShort ) )
        return error;

      sub = s->Substitute;

      if ( ACCESS_Frame( count * 2L ) )
      {
        FREE( sub );
        return error;
      }

      for ( n = 0; n < count; n++ )
        sub[n] = GET_UShort();

      FORGET_Frame();
    }

    return TT_Err_Ok;
  }


  static void  Free_Sequence( TTO_Sequence*  s )
  {
    FREE( s->Substitute );
  }


  /* MultipleSubstFormat1 */

  TT_Error  Load_MultipleSubst( TTO_MultipleSubst*  ms,
                                PFace               input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort         n, count;
    ULong          cur_offset, new_offset, base_offset;

    TTO_Sequence*  s;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 4L ) )
      return error;

    ms->SubstFormat = GET_UShort();             /* should be 1 */
    new_offset      = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &ms->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    count = ms->SequenceCount = GET_UShort();

    FORGET_Frame();

    ms->Sequence = NULL;

    if ( ALLOC_ARRAY( ms->Sequence, count, TTO_Sequence ) )
      goto Fail2;

    s = ms->Sequence;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_Sequence( &s[n], input ) ) != TT_Err_Ok )
        goto Fail1;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_Sequence( &s[n] );

    FREE( s );

  Fail2:
    Free_Coverage( &ms->Coverage );
    return error;
  }


  void  Free_MultipleSubst( TTO_MultipleSubst*  ms )
  {
    UShort         n, count;

    TTO_Sequence*  s;


    if ( ms->Sequence )
    {
      count = ms->SequenceCount;
      s     = ms->Sequence;

      for ( n = 0; n < count; n++ )
        Free_Sequence( &s[n] );

      FREE( s );
    }

    Free_Coverage( &ms->Coverage );
  }


  static TT_Error  Lookup_MultipleSubst( TTO_MultipleSubst*  ms,
                                         TTO_GSUB_String*    in,
                                         TTO_GSUB_String*    out,
                                         UShort              flags,
                                         UShort              context_length,
                                         TTO_GDEFHeader*     gdef )
  {
    TT_Error  error;
    UShort    index, property, n, count;
    UShort*   s;


    if ( context_length != 0xFFFF && context_length < 1 )
      return TTO_Err_Not_Covered;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    error = Coverage_Index( &ms->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    if ( index >= ms->SequenceCount )
      return TTO_Err_Invalid_GSUB_SubTable;

    count = ms->Sequence[index].GlyphCount;
    s     = ms->Sequence[index].Substitute;

    if ( ADD_String( in, 1, out, count, s ) )
      return error;

    if ( gdef && gdef->NewGlyphClasses )
    {
      /* this is a guess only ... */

      if ( property == TTO_LIGATURE )
        property = TTO_BASE_GLYPH;

      for ( n = 0; n < count; n++ )
      {
        error = Add_Glyph_Property( gdef, s[n], property );
        if ( error && error != TTO_Err_Not_Covered )
          return error;
      }
    }

    return TT_Err_Ok;
  }


  /* LookupType 3 */

  /* AlternateSet */

  static TT_Error  Load_AlternateSet( TTO_AlternateSet*  as,
                                      PFace              input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort   n, count;
    UShort*  a;


    if ( ACCESS_Frame( 2L ) )
      return error;

    count = as->GlyphCount = GET_UShort();

    FORGET_Frame();

    as->Alternate = NULL;

    if ( ALLOC_ARRAY( as->Alternate, count, UShort ) )
      return error;

    a = as->Alternate;

    if ( ACCESS_Frame( count * 2L ) )
    {
      FREE( a );
      return error;
    }

    for ( n = 0; n < count; n++ )
      a[n] = GET_UShort();

    FORGET_Frame();

    return TT_Err_Ok;
  }


  static void  Free_AlternateSet( TTO_AlternateSet*  as )
  {
    FREE( as->Alternate );
  }


  /* AlternateSubstFormat1 */

  TT_Error  Load_AlternateSubst( TTO_AlternateSubst*  as,
                                 PFace                input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort             n, count;
    ULong              cur_offset, new_offset, base_offset;

    TTO_AlternateSet*  aset;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 4L ) )
      return error;

    as->SubstFormat = GET_UShort();             /* should be 1 */
    new_offset      = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &as->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    count = as->AlternateSetCount = GET_UShort();

    FORGET_Frame();

    as->AlternateSet = NULL;

    if ( ALLOC_ARRAY( as->AlternateSet, count, TTO_AlternateSet ) )
      goto Fail2;

    aset = as->AlternateSet;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_AlternateSet( &aset[n], input ) ) != TT_Err_Ok )
        goto Fail1;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_AlternateSet( &aset[n] );

    FREE( aset );

  Fail2:
    Free_Coverage( &as->Coverage );
    return error;
  }


  void  Free_AlternateSubst( TTO_AlternateSubst*  as )
  {
    UShort             n, count;

    TTO_AlternateSet*  aset;


    if ( as->AlternateSet )
    {
      count = as->AlternateSetCount;
      aset  = as->AlternateSet;

      for ( n = 0; n < count; n++ )
        Free_AlternateSet( &aset[n] );

      FREE( aset );
    }

    Free_Coverage( &as->Coverage );
  }


  static TT_Error  Lookup_AlternateSubst( TTO_GSUBHeader*      gsub,
                                          TTO_AlternateSubst*  as,
                                          TTO_GSUB_String*     in,
                                          TTO_GSUB_String*     out,
                                          UShort               flags,
                                          UShort               context_length,
                                          TTO_GDEFHeader*      gdef )
  {
    TT_Error          error;
    UShort            index, alt_index, property;

    TTO_AlternateSet  aset;


    if ( context_length != 0xFFFF && context_length < 1 )
      return TTO_Err_Not_Covered;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    error = Coverage_Index( &as->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    aset = as->AlternateSet[index];

    /* we use a user-defined callback function to get the alternate index */

    if ( gsub->alt )
      alt_index = (gsub->alt)( out->pos, in->string[in->pos],
                               aset.GlyphCount, aset.Alternate,
                               gsub->data );
    else
      alt_index = 0;

    if ( ADD_String( in, 1, out, 1, &aset.Alternate[alt_index] ) )
      return error;

    if ( gdef && gdef->NewGlyphClasses )
    {
      /* we inherit the old glyph class to the substituted glyph */

      error = Add_Glyph_Property( gdef, aset.Alternate[alt_index],
                                  property );
      if ( error && error != TTO_Err_Not_Covered )
        return error;
    }

    return TT_Err_Ok;
  }


  /* LookupType 4 */

  /* Ligature */

  static TT_Error  Load_Ligature( TTO_Ligature*  l,
                                  PFace          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort   n, count;
    UShort*  c;


    if ( ACCESS_Frame( 4L ) )
      return error;

    l->LigGlyph       = GET_UShort();
    l->ComponentCount = GET_UShort();

    FORGET_Frame();

    l->Component = NULL;

    count = l->ComponentCount - 1;      /* only ComponentCount - 1 elements */

    if ( ALLOC_ARRAY( l->Component, count, UShort ) )
      return error;

    c = l->Component;

    if ( ACCESS_Frame( count * 2L ) )
    {
      FREE( c );
      return error;
    }

    for ( n = 0; n < count; n++ )
      c[n] = GET_UShort();

    FORGET_Frame();

    return TT_Err_Ok;
  }


  static void  Free_Ligature( TTO_Ligature*  l )
  {
    FREE( l->Component );
  }


  /* LigatureSet */

  static TT_Error  Load_LigatureSet( TTO_LigatureSet*  ls,
                                     PFace             input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort         n, count;
    ULong          cur_offset, new_offset, base_offset;

    TTO_Ligature*  l;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 2L ) )
      return error;

    count = ls->LigatureCount = GET_UShort();

    FORGET_Frame();

    ls->Ligature = NULL;

    if ( ALLOC_ARRAY( ls->Ligature, count, TTO_Ligature ) )
      return error;

    l = ls->Ligature;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_Ligature( &l[n], input ) ) != TT_Err_Ok )
        goto Fail;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail:
    for ( n = 0; n < count; n++ )
      Free_Ligature( &l[n] );

    FREE( l );
    return error;
  }


  static void  Free_LigatureSet( TTO_LigatureSet*  ls )
  {
    UShort         n, count;

    TTO_Ligature*  l;


    if ( ls->Ligature )
    {
      count = ls->LigatureCount;
      l     = ls->Ligature;

      for ( n = 0; n < count; n++ )
        Free_Ligature( &l[n] );

      FREE( l );
    }
  }


  /* LigatureSubstFormat1 */

  TT_Error  Load_LigatureSubst( TTO_LigatureSubst*  ls,
                                PFace               input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort            n, count;
    ULong             cur_offset, new_offset, base_offset;

    TTO_LigatureSet*  lset;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 4L ) )
      return error;

    ls->SubstFormat = GET_UShort();             /* should be 1 */
    new_offset      = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &ls->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    count = ls->LigatureSetCount = GET_UShort();

    FORGET_Frame();

    ls->LigatureSet = NULL;

    if ( ALLOC_ARRAY( ls->LigatureSet, count, TTO_LigatureSet ) )
      goto Fail2;

    lset = ls->LigatureSet;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_LigatureSet( &lset[n], input ) ) != TT_Err_Ok )
        goto Fail1;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_LigatureSet( &lset[n] );

    FREE( lset );

  Fail2:
    Free_Coverage( &ls->Coverage );
    return error;
  }


  void  Free_LigatureSubst( TTO_LigatureSubst*  ls )
  {
    UShort            n, count;

    TTO_LigatureSet*  lset;


    if ( ls->LigatureSet )
    {
      count = ls->LigatureSetCount;
      lset  = ls->LigatureSet;

      for ( n = 0; n < count; n++ )
        Free_LigatureSet( &lset[n] );

      FREE( lset );
    }

    Free_Coverage( &ls->Coverage );
  }


  static TT_Error  Lookup_LigatureSubst( TTO_LigatureSubst*  ls,
                                         TTO_GSUB_String*    in,
                                         TTO_GSUB_String*    out,
                                         UShort              flags,
                                         UShort              context_length,
                                         TTO_GDEFHeader*     gdef )
  {
    UShort         index, property;
    TT_Error       error;
    UShort         numlig, i, j;
    UShort*        s_in;
    UShort*        c;

    TTO_Ligature*  lig;


    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    error = Coverage_Index( &ls->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    if ( index >= ls->LigatureSetCount )
       return TTO_Err_Invalid_GSUB_SubTable;

    lig = ls->LigatureSet[index].Ligature;

    for ( numlig = ls->LigatureSet[index].LigatureCount;
          numlig;
          numlig--, lig++ )
    {
      if ( in->pos + lig->ComponentCount > in->length )
        continue;                         /* Not enough glyphs in input */

      s_in = &in->string[in->pos];
      c    = lig->Component;

      if ( context_length != 0xFFFF && context_length < lig->ComponentCount )
        break;

      for ( i = 1, j = 1; i < lig->ComponentCount; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( in->pos + j < in->length )
            j++;
          else
            break;
        }

        if ( s_in[j] != c[i - 1] )
          break;
      }

      if ( i == lig->ComponentCount )
      {
        if ( ADD_String( in, lig->ComponentCount, out, 1, &lig->LigGlyph ) )
          return error;

        if ( gdef && gdef->NewGlyphClasses )
        {
          /* this is just a guess ... */

          error = Add_Glyph_Property( gdef, lig->LigGlyph, TTO_LIGATURE );
          if ( error && error != TTO_Err_Not_Covered )
            return error;
        }

        return TT_Err_Ok;
      }
    }

    return TTO_Err_Not_Covered;
  }


  /* Do the actual substitution for a context substitution (either format
     5 or 6).  This is only called after we've determined that the input
     matches the subrule.                                                 */

  static TT_Error  Do_ContextSubst( TTO_GSUBHeader*         gsub,
                                    UShort                  GlyphCount,
                                    UShort                  SubstCount,
                                    TTO_SubstLookupRecord*  subst,
                                    TTO_GSUB_String*        in,
                                    TTO_GSUB_String*        out,
                                    int                     nesting_level )
  {
    TT_Error  error;
    UShort    i, old_pos;


    i = 0;

    while ( i < GlyphCount )
    {
      if ( SubstCount && i == subst->SequenceIndex )
      {
        old_pos = in->pos;

        /* Do a substitution */

        error = Do_Glyph_Lookup( gsub, subst->LookupListIndex, in, out,
                                 GlyphCount, nesting_level );

        subst++;
        SubstCount--;
        i += in->pos - old_pos;

        if ( error == TTO_Err_Not_Covered )
        {
          /* XXX "can't happen" -- but don't count on it */

          if ( ADD_String( in, 1, out, 1, &in->string[in->pos] ) )
            return error;
          i++;
        }
        else if ( error )
          return error;
      }
      else
      {
        /* No substitution for this index */

        if ( ADD_String( in, 1, out, 1, &in->string[in->pos] ) )
          return error;
        i++;
      }
    }

    return TT_Err_Ok;
  }


  /* LookupType 5 */

  /* SubRule */

  static TT_Error  Load_SubRule( TTO_SubRule*  sr,
                                 PFace         input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;
    UShort*                 i;

    TTO_SubstLookupRecord*  slr;


    if ( ACCESS_Frame( 4L ) )
      return error;

    sr->GlyphCount = GET_UShort();
    sr->SubstCount = GET_UShort();

    FORGET_Frame();

    sr->Input = NULL;

    count = sr->GlyphCount - 1;         /* only GlyphCount - 1 elements */

    if ( ALLOC_ARRAY( sr->Input, count, UShort ) )
      return error;

    i = sr->Input;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail2;

    for ( n = 0; n < count; n++ )
      i[n] = GET_UShort();

    FORGET_Frame();

    sr->SubstLookupRecord = NULL;

    count = sr->SubstCount;

    if ( ALLOC_ARRAY( sr->SubstLookupRecord, count, TTO_SubstLookupRecord ) )
      goto Fail2;

    slr = sr->SubstLookupRecord;

    if ( ACCESS_Frame( count * 4L ) )
      goto Fail1;

    for ( n = 0; n < count; n++ )
    {
      slr[n].SequenceIndex   = GET_UShort();
      slr[n].LookupListIndex = GET_UShort();
    }

    FORGET_Frame();

    return TT_Err_Ok;

  Fail1:
    FREE( slr );

  Fail2:
    FREE( i );
    return error;
  }


  static void  Free_SubRule( TTO_SubRule*  sr )
  {
    FREE( sr->SubstLookupRecord );
    FREE( sr->Input );
  }


  /* SubRuleSet */

  static TT_Error  Load_SubRuleSet( TTO_SubRuleSet*  srs,
                                    PFace            input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort        n, count;
    ULong         cur_offset, new_offset, base_offset;

    TTO_SubRule*  sr;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 2L ) )
      return error;

    count = srs->SubRuleCount = GET_UShort();

    FORGET_Frame();

    srs->SubRule = NULL;

    if ( ALLOC_ARRAY( srs->SubRule, count, TTO_SubRule ) )
      return error;

    sr = srs->SubRule;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_SubRule( &sr[n], input ) ) != TT_Err_Ok )
        goto Fail;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail:
    for ( n = 0; n < count; n++ )
      Free_SubRule( &sr[n] );

    FREE( sr );
    return error;
  }


  static void  Free_SubRuleSet( TTO_SubRuleSet*  srs )
  {
    UShort        n, count;

    TTO_SubRule*  sr;


    if ( srs->SubRule )
    {
      count = srs->SubRuleCount;
      sr    = srs->SubRule;

      for ( n = 0; n < count; n++ )
        Free_SubRule( &sr[n] );

      FREE( sr );
    }
  }


  /* ContextSubstFormat1 */

  static TT_Error  Load_ContextSubst1( TTO_ContextSubstFormat1*  csf1,
                                       PFace                     input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort           n, count;
    ULong            cur_offset, new_offset, base_offset;

    TTO_SubRuleSet*  srs;


    base_offset = FILE_Pos() - 2L;

    if ( ACCESS_Frame( 2L ) )
      return error;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &csf1->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    count = csf1->SubRuleSetCount = GET_UShort();

    FORGET_Frame();

    csf1->SubRuleSet = NULL;

    if ( ALLOC_ARRAY( csf1->SubRuleSet, count, TTO_SubRuleSet ) )
      goto Fail2;

    srs = csf1->SubRuleSet;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_SubRuleSet( &srs[n], input ) ) != TT_Err_Ok )
        goto Fail1;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_SubRuleSet( &srs[n] );

    FREE( srs );

  Fail2:
    Free_Coverage( &csf1->Coverage );
    return error;
  }


  static void  Free_Context1( TTO_ContextSubstFormat1*  csf1 )
  {
    UShort           n, count;

    TTO_SubRuleSet*  srs;


    if ( csf1->SubRuleSet )
    {
      count = csf1->SubRuleSetCount;
      srs   = csf1->SubRuleSet;

      for ( n = 0; n < count; n++ )
        Free_SubRuleSet( &srs[n] );

      FREE( srs );
    }

    Free_Coverage( &csf1->Coverage );
  }


  /* SubClassRule */

  static TT_Error  Load_SubClassRule( TTO_ContextSubstFormat2*  csf2,
                                      TTO_SubClassRule*         scr,
                                      PFace                     input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;

    UShort*                 c;
    TTO_SubstLookupRecord*  slr;
    Bool*                   d;


    if ( ACCESS_Frame( 4L ) )
      return error;

    scr->GlyphCount = GET_UShort();
    scr->SubstCount = GET_UShort();

    if ( scr->GlyphCount > csf2->MaxContextLength )
      csf2->MaxContextLength = scr->GlyphCount;

    FORGET_Frame();

    scr->Class = NULL;

    count = scr->GlyphCount - 1;        /* only GlyphCount - 1 elements */

    if ( ALLOC_ARRAY( scr->Class, count, UShort ) )
      return error;

    c = scr->Class;
    d = csf2->ClassDef.Defined;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail2;

    for ( n = 0; n < count; n++ )
    {
      c[n] = GET_UShort();

      /* We check whether the specific class is used at all.  If not,
         class 0 is used instead.                                     */

      if ( !d[c[n]] )
        c[n] = 0;
    }

    FORGET_Frame();

    scr->SubstLookupRecord = NULL;

    count = scr->SubstCount;

    if ( ALLOC_ARRAY( scr->SubstLookupRecord, count, TTO_SubstLookupRecord ) )
      goto Fail2;

    slr = scr->SubstLookupRecord;

    if ( ACCESS_Frame( count * 4L ) )
      goto Fail1;

    for ( n = 0; n < count; n++ )
    {
      slr[n].SequenceIndex   = GET_UShort();
      slr[n].LookupListIndex = GET_UShort();
    }

    FORGET_Frame();

    return TT_Err_Ok;

  Fail1:
    FREE( slr );

  Fail2:
    FREE( c );
    return error;
  }


  static void  Free_SubClassRule( TTO_SubClassRule*  scr )
  {
    FREE( scr->SubstLookupRecord );
    FREE( scr->Class );
  }


  /* SubClassSet */

  static TT_Error  Load_SubClassSet( TTO_ContextSubstFormat2*  csf2,
                                     TTO_SubClassSet*          scs,
                                     PFace                     input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort             n, count;
    ULong              cur_offset, new_offset, base_offset;

    TTO_SubClassRule*  scr;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 2L ) )
      return error;

    count = scs->SubClassRuleCount = GET_UShort();

    FORGET_Frame();

    scs->SubClassRule = NULL;

    if ( ALLOC_ARRAY( scs->SubClassRule, count, TTO_SubClassRule ) )
      return error;

    scr = scs->SubClassRule;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_SubClassRule( csf2, &scr[n],
                                        input ) ) != TT_Err_Ok )
        goto Fail;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail:
    for ( n = 0; n < count; n++ )
      Free_SubClassRule( &scr[n] );

    FREE( scr );
    return error;
  }


  static void  Free_SubClassSet( TTO_SubClassSet*  scs )
  {
    UShort             n, count;

    TTO_SubClassRule*  scr;


    if ( scs->SubClassRule )
    {
      count = scs->SubClassRuleCount;
      scr   = scs->SubClassRule;

      for ( n = 0; n < count; n++ )
        Free_SubClassRule( &scr[n] );

      FREE( scr );
    }
  }


  /* ContextSubstFormat2 */

  static TT_Error  Load_ContextSubst2( TTO_ContextSubstFormat2*  csf2,
                                       PFace                     input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort            n, count;
    ULong             cur_offset, new_offset, base_offset;

    TTO_SubClassSet*  scs;


    base_offset = FILE_Pos() - 2;

    if ( ACCESS_Frame( 2L ) )
      return error;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &csf2->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 4L ) )
      goto Fail3;

    new_offset = GET_UShort() + base_offset;

    /* `SubClassSetCount' is the upper limit for class values, thus we
       read it now to make an additional safety check.                 */

    count = csf2->SubClassSetCount = GET_UShort();

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_ClassDefinition( &csf2->ClassDef, count,
                                         input ) ) != TT_Err_Ok )
      goto Fail3;
    (void)FILE_Seek( cur_offset );

    csf2->SubClassSet      = NULL;
    csf2->MaxContextLength = 0;

    if ( ALLOC_ARRAY( csf2->SubClassSet, count, TTO_SubClassSet ) )
      goto Fail2;

    scs = csf2->SubClassSet;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      if ( new_offset != base_offset )      /* not a NULL offset */
      {
        cur_offset = FILE_Pos();
        if ( FILE_Seek( new_offset ) ||
             ( error = Load_SubClassSet( csf2, &scs[n],
                                         input ) ) != TT_Err_Ok )
          goto Fail1;
        (void)FILE_Seek( cur_offset );
      }
      else
      {
        /* we create a SubClassSet table with no entries */

        csf2->SubClassSet[n].SubClassRuleCount = 0;
        csf2->SubClassSet[n].SubClassRule      = NULL;
      }
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_SubClassSet( &scs[n] );

    FREE( scs );

  Fail2:
    Free_ClassDefinition( &csf2->ClassDef );

  Fail3:
    Free_Coverage( &csf2->Coverage );
    return error;
  }


  static void  Free_Context2( TTO_ContextSubstFormat2*  csf2 )
  {
    UShort            n, count;

    TTO_SubClassSet*  scs;


    if ( csf2->SubClassSet )
    {
      count = csf2->SubClassSetCount;
      scs   = csf2->SubClassSet;

      for ( n = 0; n < count; n++ )
        Free_SubClassSet( &scs[n] );

      FREE( scs );
    }

    Free_ClassDefinition( &csf2->ClassDef );
    Free_Coverage( &csf2->Coverage );
  }


  /* ContextSubstFormat3 */

  static TT_Error  Load_ContextSubst3( TTO_ContextSubstFormat3*  csf3,
                                       PFace                     input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;
    ULong                   cur_offset, new_offset, base_offset;

    TTO_Coverage*           c;
    TTO_SubstLookupRecord*  slr;


    base_offset = FILE_Pos() - 2L;

    if ( ACCESS_Frame( 4L ) )
      return error;

    csf3->GlyphCount = GET_UShort();
    csf3->SubstCount = GET_UShort();

    FORGET_Frame();

    csf3->Coverage = NULL;

    count = csf3->GlyphCount;

    if ( ALLOC_ARRAY( csf3->Coverage, count, TTO_Coverage ) )
      return error;

    c = csf3->Coverage;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail2;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_Coverage( &c[n], input ) ) != TT_Err_Ok )
        goto Fail2;
      (void)FILE_Seek( cur_offset );
    }

    csf3->SubstLookupRecord = NULL;

    count = csf3->SubstCount;

    if ( ALLOC_ARRAY( csf3->SubstLookupRecord, count,
                      TTO_SubstLookupRecord ) )
      goto Fail2;

    slr = csf3->SubstLookupRecord;

    if ( ACCESS_Frame( count * 4L ) )
      goto Fail1;

    for ( n = 0; n < count; n++ )
    {
      slr[n].SequenceIndex   = GET_UShort();
      slr[n].LookupListIndex = GET_UShort();
    }

    FORGET_Frame();

    return TT_Err_Ok;

  Fail1:
    FREE( slr );

  Fail2:
    for ( n = 0; n < count; n++ )
      Free_Coverage( &c[n] );

    FREE( c );
    return error;
  }


  static void  Free_Context3( TTO_ContextSubstFormat3*  csf3 )
  {
    UShort         n, count;

    TTO_Coverage*  c;


    FREE( csf3->SubstLookupRecord );

    if ( csf3->Coverage )
    {
      count = csf3->GlyphCount;
      c     = csf3->Coverage;

      for ( n = 0; n < count; n++ )
        Free_Coverage( &c[n] );

      FREE( c );
    }
  }


  /* ContextSubst */

  TT_Error  Load_ContextSubst( TTO_ContextSubst*  cs,
                               PFace              input )
  {
    DEFINE_LOAD_LOCALS( input->stream );


    if ( ACCESS_Frame( 2L ) )
      return error;

    cs->SubstFormat = GET_UShort();

    FORGET_Frame();

    switch ( cs->SubstFormat )
    {
    case 1:
      return Load_ContextSubst1( &cs->csf.csf1, input );

    case 2:
      return Load_ContextSubst2( &cs->csf.csf2, input );

    case 3:
      return Load_ContextSubst3( &cs->csf.csf3, input );

    default:
      return TTO_Err_Invalid_GSUB_SubTable_Format;
    }

    return TT_Err_Ok;               /* never reached */
  }


  void  Free_ContextSubst( TTO_ContextSubst*  cs )
  {
    switch ( cs->SubstFormat )
    {
    case 1:
      Free_Context1( &cs->csf.csf1 );
      break;

    case 2:
      Free_Context2( &cs->csf.csf2 );
      break;

    case 3:
      Free_Context3( &cs->csf.csf3 );
      break;
    }
  }


  static TT_Error  Lookup_ContextSubst1(
                     TTO_GSUBHeader*           gsub,
                     TTO_ContextSubstFormat1*  csf1,
                     TTO_GSUB_String*          in,
                     TTO_GSUB_String*          out,
                     UShort                    flags,
                     UShort                    context_length,
                     int                       nesting_level )
  {
    UShort           index, property;
    UShort           i, j, k, numsr;
    TT_Error         error;
    UShort*          s_in;

    TTO_SubRule*     sr;
    TTO_GDEFHeader*  gdef;


    gdef = gsub->gdef;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    error = Coverage_Index( &csf1->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    sr    = csf1->SubRuleSet[index].SubRule;
    numsr = csf1->SubRuleSet[index].SubRuleCount;

    for ( k = 0; k < numsr; k++ )
    {
      if ( context_length != 0xFFFF && context_length < sr[k].GlyphCount )
        continue;

      if ( in->pos + sr[k].GlyphCount > in->length )
        continue;                           /* context is too long */

      s_in = &in->string[in->pos];

      for ( i = 1, j = 1; i < sr[k].GlyphCount; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( in->pos + j < in->length )
            j++;
          else
            break;
        }

        if ( s_in[j] != sr[k].Input[i - 1] )
          break;
      }

      if ( i == sr[k].GlyphCount )
        return Do_ContextSubst( gsub, sr[k].GlyphCount,
                                sr[k].SubstCount, sr[k].SubstLookupRecord,
                                in, out,
                                nesting_level );
    }

    return TTO_Err_Not_Covered;
  }


  static TT_Error  Lookup_ContextSubst2(
                     TTO_GSUBHeader*           gsub,
                     TTO_ContextSubstFormat2*  csf2,
                     TTO_GSUB_String*          in,
                     TTO_GSUB_String*          out,
                     UShort                    flags,
                     UShort                    context_length,
                     int                       nesting_level )
  {
    UShort             index, property;
    TT_Error           error;
    UShort             i, j, k, known_classes;

    UShort*            classes;
    UShort*            s_in;
    UShort*            cl;

    TTO_SubClassSet*   scs;
    TTO_SubClassRule*  sr;
    TTO_GDEFHeader*    gdef;


    gdef = gsub->gdef;

    if ( ALLOC_ARRAY( classes, csf2->MaxContextLength, UShort ) )
      return error;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    /* Note: The coverage table in format 2 doesn't give an index into
             anything.  It just lets us know whether or not we need to
             do any lookup at all.                                     */

    error = Coverage_Index( &csf2->Coverage, in->string[in->pos], &index );
    if ( error )
      goto End;

    error = Get_Class( &csf2->ClassDef, in->string[in->pos],
                       &classes[0], NULL );
    if ( error )
      goto End;
    known_classes = 0;

    scs = &csf2->SubClassSet[classes[0]];
    if ( !scs )
    {
      error = TTO_Err_Invalid_GSUB_SubTable;
      goto End;
    }

    for ( k = 0; k < scs->SubClassRuleCount; k++ )
    {
      sr  = &scs->SubClassRule[k];

      if ( context_length != 0xFFFF && context_length < sr->GlyphCount )
        continue;

      if ( in->pos + sr->GlyphCount > in->length )
        continue;                           /* context is too long */

      s_in = &in->string[in->pos];
      cl   = sr->Class;

      /* Start at 1 because [0] is implied */

      for ( i = 1, j = 1; i < sr->GlyphCount; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( in->pos + j < in->length )
            j++;
          else
            break;
        }

        if ( i > known_classes )
        {
          /* Keeps us from having to do this for each rule */

          error = Get_Class( &csf2->ClassDef, s_in[j], &classes[i], NULL );
          if ( error && error != TTO_Err_Not_Covered )
            return error;
          known_classes = i;
        }

        if ( cl[i - 1] != classes[i] )
          break;
      }

      if ( i == sr->GlyphCount )
      {
        error = Do_ContextSubst( gsub, sr->GlyphCount,
                                 sr->SubstCount, sr->SubstLookupRecord,
                                 in, out,
                                 nesting_level );
        goto End;
      }
    }

    error = TTO_Err_Not_Covered;

  End:
    FREE( classes );
    return error;
  }


  static TT_Error  Lookup_ContextSubst3(
                     TTO_GSUBHeader*           gsub,
                     TTO_ContextSubstFormat3*  csf3,
                     TTO_GSUB_String*          in,
                     TTO_GSUB_String*          out,
                     UShort                    flags,
                     UShort                    context_length,
                     int                       nesting_level )
  {
    TT_Error         error;
    UShort           index, i, j, property;
    UShort*          s_in;

    TTO_Coverage*    c;
    TTO_GDEFHeader*  gdef;


    gdef = gsub->gdef;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    if ( context_length != 0xFFFF && context_length < csf3->GlyphCount )
      return TTO_Err_Not_Covered;

    if ( in->pos + csf3->GlyphCount > in->length )
      return TTO_Err_Not_Covered;         /* context is too long */

    s_in = &in->string[in->pos];
    c    = csf3->Coverage;

    for ( i = 1, j = 1; i < csf3->GlyphCount; i++, j++ )
    {
      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
      {
        if ( error && error != TTO_Err_Not_Covered )
          return error;

        if ( in->pos + j < in->length )
          j++;
        else
          return TTO_Err_Not_Covered;
      }

      error = Coverage_Index( &c[i], s_in[j], &index );
      if ( error )
        return error;
    }

    return Do_ContextSubst( gsub, csf3->GlyphCount,
                            csf3->SubstCount, csf3->SubstLookupRecord,
                            in, out,
                            nesting_level );
  }


  static TT_Error  Lookup_ContextSubst( TTO_GSUBHeader*    gsub,
                                        TTO_ContextSubst*  cs,
                                        TTO_GSUB_String*   in,
                                        TTO_GSUB_String*   out,
                                        UShort             flags,
                                        UShort             context_length,
                                        int                nesting_level )
  {
    switch ( cs->SubstFormat )
    {
    case 1:
      return Lookup_ContextSubst1( gsub, &cs->csf.csf1, in, out,
                                   flags, context_length, nesting_level );

    case 2:
      return Lookup_ContextSubst2( gsub, &cs->csf.csf2, in, out,
                                   flags, context_length, nesting_level );

    case 3:
      return Lookup_ContextSubst3( gsub, &cs->csf.csf3, in, out,
                                   flags, context_length, nesting_level );

    default:
      return TTO_Err_Invalid_GSUB_SubTable_Format;
    }

    return TT_Err_Ok;               /* never reached */
  }


  /* LookupType 6 */

  /* ChainSubRule */

  static TT_Error  Load_ChainSubRule( TTO_ChainSubRule*  csr,
                                      PFace              input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;
    UShort*                 b;
    UShort*                 i;
    UShort*                 l;

    TTO_SubstLookupRecord*  slr;


    if ( ACCESS_Frame( 2L ) )
      return error;

    csr->BacktrackGlyphCount = GET_UShort();

    FORGET_Frame();

    csr->Backtrack = NULL;

    count = csr->BacktrackGlyphCount;

    if ( ALLOC_ARRAY( csr->Backtrack, count, UShort ) )
      return error;

    b = csr->Backtrack;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail4;

    for ( n = 0; n < count; n++ )
      b[n] = GET_UShort();

    FORGET_Frame();

    if ( ACCESS_Frame( 2L ) )
      goto Fail4;

    csr->InputGlyphCount = GET_UShort();

    FORGET_Frame();

    csr->Input = NULL;

    count = csr->InputGlyphCount - 1;  /* only InputGlyphCount - 1 elements */

    if ( ALLOC_ARRAY( csr->Input, count, UShort ) )
      goto Fail4;

    i = csr->Input;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail3;

    for ( n = 0; n < count; n++ )
      i[n] = GET_UShort();

    FORGET_Frame();

    if ( ACCESS_Frame( 2L ) )
      goto Fail3;

    csr->LookaheadGlyphCount = GET_UShort();

    FORGET_Frame();

    csr->Lookahead = NULL;

    count = csr->LookaheadGlyphCount;

    if ( ALLOC_ARRAY( csr->Lookahead, count, UShort ) )
      goto Fail3;

    l = csr->Lookahead;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail2;

    for ( n = 0; n < count; n++ )
      l[n] = GET_UShort();

    FORGET_Frame();

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    csr->SubstCount = GET_UShort();

    FORGET_Frame();

    csr->SubstLookupRecord = NULL;

    count = csr->SubstCount;

    if ( ALLOC_ARRAY( csr->SubstLookupRecord, count, TTO_SubstLookupRecord ) )
      goto Fail2;

    slr = csr->SubstLookupRecord;

    if ( ACCESS_Frame( count * 4L ) )
      goto Fail1;

    for ( n = 0; n < count; n++ )
    {
      slr[n].SequenceIndex   = GET_UShort();
      slr[n].LookupListIndex = GET_UShort();
    }

    FORGET_Frame();

    return TT_Err_Ok;

  Fail1:
    FREE( slr );

  Fail2:
    FREE( l );

  Fail3:
    FREE( i );

  Fail4:
    FREE( b );
    return error;
  }


  static void  Free_ChainSubRule( TTO_ChainSubRule*  csr )
  {
    FREE( csr->SubstLookupRecord );
    FREE( csr->Lookahead );
    FREE( csr->Input );
    FREE( csr->Backtrack );
  }


  /* ChainSubRuleSet */

  static TT_Error  Load_ChainSubRuleSet( TTO_ChainSubRuleSet*  csrs,
                                         PFace                 input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort             n, count;
    ULong              cur_offset, new_offset, base_offset;

    TTO_ChainSubRule*  csr;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 2L ) )
      return error;

    count = csrs->ChainSubRuleCount = GET_UShort();

    FORGET_Frame();

    csrs->ChainSubRule = NULL;

    if ( ALLOC_ARRAY( csrs->ChainSubRule, count, TTO_ChainSubRule ) )
      return error;

    csr = csrs->ChainSubRule;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_ChainSubRule( &csr[n], input ) ) != TT_Err_Ok )
        goto Fail;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail:
    for ( n = 0; n < count; n++ )
      Free_ChainSubRule( &csr[n] );

    FREE( csr );
    return error;
  }


  static void  Free_ChainSubRuleSet( TTO_ChainSubRuleSet*  csrs )
  {
    UShort             n, count;

    TTO_ChainSubRule*  csr;


    if ( csrs->ChainSubRule )
    {
      count = csrs->ChainSubRuleCount;
      csr   = csrs->ChainSubRule;

      for ( n = 0; n < count; n++ )
        Free_ChainSubRule( &csr[n] );

      FREE( csr );
    }
  }


  /* ChainContextSubstFormat1 */

  static TT_Error  Load_ChainContextSubst1(
                     TTO_ChainContextSubstFormat1*  ccsf1,
                     PFace                          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                n, count;
    ULong                 cur_offset, new_offset, base_offset;

    TTO_ChainSubRuleSet*  csrs;


    base_offset = FILE_Pos() - 2L;

    if ( ACCESS_Frame( 2L ) )
      return error;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &ccsf1->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    count = ccsf1->ChainSubRuleSetCount = GET_UShort();

    FORGET_Frame();

    ccsf1->ChainSubRuleSet = NULL;

    if ( ALLOC_ARRAY( ccsf1->ChainSubRuleSet, count, TTO_ChainSubRuleSet ) )
      goto Fail2;

    csrs = ccsf1->ChainSubRuleSet;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_ChainSubRuleSet( &csrs[n], input ) ) != TT_Err_Ok )
        goto Fail1;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_ChainSubRuleSet( &csrs[n] );

    FREE( csrs );

  Fail2:
    Free_Coverage( &ccsf1->Coverage );
    return error;
  }


  static void  Free_ChainContext1( TTO_ChainContextSubstFormat1*  ccsf1 )
  {
    UShort                n, count;

    TTO_ChainSubRuleSet*  csrs;


    if ( ccsf1->ChainSubRuleSet )
    {
      count = ccsf1->ChainSubRuleSetCount;
      csrs  = ccsf1->ChainSubRuleSet;

      for ( n = 0; n < count; n++ )
        Free_ChainSubRuleSet( &csrs[n] );

      FREE( csrs );
    }

    Free_Coverage( &ccsf1->Coverage );
  }


  /* ChainSubClassRule */

  static TT_Error  Load_ChainSubClassRule(
                     TTO_ChainContextSubstFormat2*  ccsf2,
                     TTO_ChainSubClassRule*         cscr,
                     PFace                          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;

    UShort*                 b;
    UShort*                 i;
    UShort*                 l;
    TTO_SubstLookupRecord*  slr;
    Bool*                   d;


    if ( ACCESS_Frame( 2L ) )
      return error;

    cscr->BacktrackGlyphCount = GET_UShort();

    FORGET_Frame();

    if ( cscr->BacktrackGlyphCount > ccsf2->MaxBacktrackLength )
      ccsf2->MaxBacktrackLength = cscr->BacktrackGlyphCount;

    cscr->Backtrack = NULL;

    count = cscr->BacktrackGlyphCount;

    if ( ALLOC_ARRAY( cscr->Backtrack, count, UShort ) )
      return error;

    b = cscr->Backtrack;
    d = ccsf2->BacktrackClassDef.Defined;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail4;

    for ( n = 0; n < count; n++ )
    {
      b[n] = GET_UShort();

      /* We check whether the specific class is used at all.  If not,
         class 0 is used instead.                                     */

      if ( !d[b[n]] )
        b[n] = 0;
    }

    FORGET_Frame();

    if ( ACCESS_Frame( 2L ) )
      goto Fail4;

    cscr->InputGlyphCount = GET_UShort();

    FORGET_Frame();

    if ( cscr->InputGlyphCount > ccsf2->MaxInputLength )
      ccsf2->MaxInputLength = cscr->InputGlyphCount;

    cscr->Input = NULL;

    count = cscr->InputGlyphCount - 1; /* only InputGlyphCount - 1 elements */

    if ( ALLOC_ARRAY( cscr->Input, count, UShort ) )
      goto Fail4;

    i = cscr->Input;
    d = ccsf2->InputClassDef.Defined;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail3;

    for ( n = 0; n < count; n++ )
    {
      i[n] = GET_UShort();

      if ( !d[i[n]] )
        i[n] = 0;
    }

    FORGET_Frame();

    if ( ACCESS_Frame( 2L ) )
      goto Fail3;

    cscr->LookaheadGlyphCount = GET_UShort();

    FORGET_Frame();

    if ( cscr->LookaheadGlyphCount > ccsf2->MaxLookaheadLength )
      ccsf2->MaxLookaheadLength = cscr->LookaheadGlyphCount;

    cscr->Lookahead = NULL;

    count = cscr->LookaheadGlyphCount;

    if ( ALLOC_ARRAY( cscr->Lookahead, count, UShort ) )
      goto Fail3;

    l = cscr->Lookahead;
    d = ccsf2->LookaheadClassDef.Defined;

    if ( ACCESS_Frame( count * 2L ) )
      goto Fail2;

    for ( n = 0; n < count; n++ )
    {
      l[n] = GET_UShort();

      if ( !d[l[n]] )
        l[n] = 0;
    }

    FORGET_Frame();

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    cscr->SubstCount = GET_UShort();

    FORGET_Frame();

    cscr->SubstLookupRecord = NULL;

    count = cscr->SubstCount;

    if ( ALLOC_ARRAY( cscr->SubstLookupRecord, count,
                      TTO_SubstLookupRecord ) )
      goto Fail2;

    slr = cscr->SubstLookupRecord;

    if ( ACCESS_Frame( count * 4L ) )
      goto Fail1;

    for ( n = 0; n < count; n++ )
    {
      slr[n].SequenceIndex   = GET_UShort();
      slr[n].LookupListIndex = GET_UShort();
    }

    FORGET_Frame();

    return TT_Err_Ok;

  Fail1:
    FREE( slr );

  Fail2:
    FREE( l );

  Fail3:
    FREE( i );

  Fail4:
    FREE( b );
    return error;
  }


  static void  Free_ChainSubClassRule( TTO_ChainSubClassRule*  cscr )
  {
    FREE( cscr->SubstLookupRecord );
    FREE( cscr->Lookahead );
    FREE( cscr->Input );
    FREE( cscr->Backtrack );
  }


  /* SubClassSet */

  static TT_Error  Load_ChainSubClassSet(
                     TTO_ChainContextSubstFormat2*  ccsf2,
                     TTO_ChainSubClassSet*          cscs,
                     PFace                          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;
    ULong                   cur_offset, new_offset, base_offset;

    TTO_ChainSubClassRule*  cscr;


    base_offset = FILE_Pos();

    if ( ACCESS_Frame( 2L ) )
      return error;

    count = cscs->ChainSubClassRuleCount = GET_UShort();

    FORGET_Frame();

    cscs->ChainSubClassRule = NULL;

    if ( ALLOC_ARRAY( cscs->ChainSubClassRule, count,
                      TTO_ChainSubClassRule ) )
      return error;

    cscr = cscs->ChainSubClassRule;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_ChainSubClassRule( ccsf2, &cscr[n],
                                             input ) ) != TT_Err_Ok )
        goto Fail;
      (void)FILE_Seek( cur_offset );
    }

    return TT_Err_Ok;

  Fail:
    for ( n = 0; n < count; n++ )
      Free_ChainSubClassRule( &cscr[n] );

    FREE( cscr );
    return error;
  }


  static void  Free_ChainSubClassSet( TTO_ChainSubClassSet*  cscs )
  {
    UShort                  n, count;

    TTO_ChainSubClassRule*  cscr;


    if ( cscs->ChainSubClassRule )
    {
      count = cscs->ChainSubClassRuleCount;
      cscr  = cscs->ChainSubClassRule;

      for ( n = 0; n < count; n++ )
        Free_ChainSubClassRule( &cscr[n] );

      FREE( cscr );
    }
  }


  /* ChainContextSubstFormat2 */

  static TT_Error  Load_ChainContextSubst2(
                     TTO_ChainContextSubstFormat2*  ccsf2,
                     PFace                          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                 n, count;
    ULong                  cur_offset, new_offset, base_offset;
    ULong                  backtrack_offset, input_offset, lookahead_offset;

    TTO_ChainSubClassSet*  cscs;


    base_offset = FILE_Pos() - 2;

    if ( ACCESS_Frame( 2L ) )
      return error;

    new_offset = GET_UShort() + base_offset;

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( new_offset ) ||
         ( error = Load_Coverage( &ccsf2->Coverage, input ) ) != TT_Err_Ok )
      return error;
    (void)FILE_Seek( cur_offset );

    if ( ACCESS_Frame( 8L ) )
      goto Fail5;

    backtrack_offset = GET_UShort() + base_offset;
    input_offset     = GET_UShort() + base_offset;
    lookahead_offset = GET_UShort() + base_offset;

    /* `ChainSubClassSetCount' is the upper limit for input class values,
       thus we read it now to make an additional safety check.            */

    count = ccsf2->ChainSubClassSetCount = GET_UShort();

    FORGET_Frame();

    cur_offset = FILE_Pos();
    if ( FILE_Seek( backtrack_offset ) ||
         ( error = Load_ClassDefinition( &ccsf2->BacktrackClassDef, count,
                                         input ) ) != TT_Err_Ok )
      goto Fail5;
    if ( FILE_Seek( input_offset ) ||
         ( error = Load_ClassDefinition( &ccsf2->InputClassDef, count,
                                         input ) ) != TT_Err_Ok )
      goto Fail4;
    if ( FILE_Seek( lookahead_offset ) ||
         ( error = Load_ClassDefinition( &ccsf2->LookaheadClassDef, count,
                                         input ) ) != TT_Err_Ok )
      goto Fail3;
    (void)FILE_Seek( cur_offset );

    ccsf2->ChainSubClassSet   = NULL;
    ccsf2->MaxBacktrackLength = 0;
    ccsf2->MaxInputLength     = 0;
    ccsf2->MaxLookaheadLength = 0;

    if ( ALLOC_ARRAY( ccsf2->ChainSubClassSet, count, TTO_ChainSubClassSet ) )
      goto Fail2;

    cscs = ccsf2->ChainSubClassSet;

    for ( n = 0; n < count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail1;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      if ( new_offset != base_offset )      /* not a NULL offset */
      {
        cur_offset = FILE_Pos();
        if ( FILE_Seek( new_offset ) ||
             ( error = Load_ChainSubClassSet( ccsf2, &cscs[n],
                                              input ) ) != TT_Err_Ok )
          goto Fail1;
        (void)FILE_Seek( cur_offset );
      }
      else
      {
        /* we create a ChainSubClassSet table with no entries */

        ccsf2->ChainSubClassSet[n].ChainSubClassRuleCount = 0;
        ccsf2->ChainSubClassSet[n].ChainSubClassRule      = NULL;
      }
    }

    return TT_Err_Ok;

  Fail1:
    for ( n = 0; n < count; n++ )
      Free_ChainSubClassSet( &cscs[n] );

    FREE( cscs );

  Fail2:
    Free_ClassDefinition( &ccsf2->LookaheadClassDef );

  Fail3:
    Free_ClassDefinition( &ccsf2->InputClassDef );

  Fail4:
    Free_ClassDefinition( &ccsf2->BacktrackClassDef );

  Fail5:
    Free_Coverage( &ccsf2->Coverage );
    return error;
  }


  static void  Free_ChainContext2( TTO_ChainContextSubstFormat2*  ccsf2 )
  {
    UShort                 n, count;

    TTO_ChainSubClassSet*  cscs;


    if ( ccsf2->ChainSubClassSet )
    {
      count = ccsf2->ChainSubClassSetCount;
      cscs  = ccsf2->ChainSubClassSet;

      for ( n = 0; n < count; n++ )
        Free_ChainSubClassSet( &cscs[n] );

      FREE( cscs );
    }

    Free_ClassDefinition( &ccsf2->LookaheadClassDef );
    Free_ClassDefinition( &ccsf2->InputClassDef );
    Free_ClassDefinition( &ccsf2->BacktrackClassDef );

    Free_Coverage( &ccsf2->Coverage );
  }


  /* ChainContextSubstFormat3 */

  static TT_Error  Load_ChainContextSubst3(
                     TTO_ChainContextSubstFormat3*  ccsf3,
                     PFace                          input )
  {
    DEFINE_LOAD_LOCALS( input->stream );

    UShort                  n, count;
    UShort                  backtrack_count, input_count, lookahead_count;
    ULong                   cur_offset, new_offset, base_offset;

    TTO_Coverage*           b;
    TTO_Coverage*           i;
    TTO_Coverage*           l;
    TTO_SubstLookupRecord*  slr;


    base_offset = FILE_Pos() - 2L;

    if ( ACCESS_Frame( 2L ) )
      return error;

    ccsf3->BacktrackGlyphCount = GET_UShort();

    FORGET_Frame();

    ccsf3->BacktrackCoverage = NULL;

    backtrack_count = ccsf3->BacktrackGlyphCount;

    if ( ALLOC_ARRAY( ccsf3->BacktrackCoverage, backtrack_count,
                      TTO_Coverage ) )
      return error;

    b = ccsf3->BacktrackCoverage;

    for ( n = 0; n < backtrack_count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail4;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_Coverage( &b[n], input ) ) != TT_Err_Ok )
        goto Fail4;
      (void)FILE_Seek( cur_offset );
    }

    if ( ACCESS_Frame( 2L ) )
      goto Fail4;

    ccsf3->InputGlyphCount = GET_UShort();

    FORGET_Frame();

    ccsf3->InputCoverage = NULL;

    input_count = ccsf3->InputGlyphCount;

    if ( ALLOC_ARRAY( ccsf3->InputCoverage, input_count, TTO_Coverage ) )
      goto Fail4;

    i = ccsf3->InputCoverage;

    for ( n = 0; n < input_count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail3;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_Coverage( &i[n], input ) ) != TT_Err_Ok )
        goto Fail3;
      (void)FILE_Seek( cur_offset );
    }

    if ( ACCESS_Frame( 2L ) )
      goto Fail3;

    ccsf3->LookaheadGlyphCount = GET_UShort();

    FORGET_Frame();

    ccsf3->LookaheadCoverage = NULL;

    lookahead_count = ccsf3->LookaheadGlyphCount;

    if ( ALLOC_ARRAY( ccsf3->LookaheadCoverage, lookahead_count,
                      TTO_Coverage ) )
      goto Fail3;

    l = ccsf3->LookaheadCoverage;

    for ( n = 0; n < lookahead_count; n++ )
    {
      if ( ACCESS_Frame( 2L ) )
        goto Fail2;

      new_offset = GET_UShort() + base_offset;

      FORGET_Frame();

      cur_offset = FILE_Pos();
      if ( FILE_Seek( new_offset ) ||
           ( error = Load_Coverage( &l[n], input ) ) != TT_Err_Ok )
        goto Fail2;
      (void)FILE_Seek( cur_offset );
    }

    if ( ACCESS_Frame( 2L ) )
      goto Fail2;

    ccsf3->SubstCount = GET_UShort();

    FORGET_Frame();

    ccsf3->SubstLookupRecord = NULL;

    count = ccsf3->SubstCount;

    if ( ALLOC_ARRAY( ccsf3->SubstLookupRecord, count,
                      TTO_SubstLookupRecord ) )
      goto Fail2;

    slr = ccsf3->SubstLookupRecord;

    if ( ACCESS_Frame( count * 4L ) )
      goto Fail1;

    for ( n = 0; n < count; n++ )
    {
      slr[n].SequenceIndex   = GET_UShort();
      slr[n].LookupListIndex = GET_UShort();
    }

    FORGET_Frame();

    return TT_Err_Ok;

  Fail1:
    FREE( slr );

  Fail2:
    for ( n = 0; n < lookahead_count; n++ )
      Free_Coverage( &l[n] );

    FREE( l );

  Fail3:
    for ( n = 0; n < input_count; n++ )
      Free_Coverage( &i[n] );

    FREE( i );

  Fail4:
    for ( n = 0; n < backtrack_count; n++ )
      Free_Coverage( &b[n] );

    FREE( b );
    return error;
  }


  static void  Free_ChainContext3( TTO_ChainContextSubstFormat3*  ccsf3 )
  {
    UShort         n, count;

    TTO_Coverage*  c;


    FREE( ccsf3->SubstLookupRecord );

    if ( ccsf3->LookaheadCoverage )
    {
      count = ccsf3->LookaheadGlyphCount;
      c     = ccsf3->LookaheadCoverage;

      for ( n = 0; n < count; n++ )
        Free_Coverage( &c[n] );

      FREE( c );
    }

    if ( ccsf3->InputCoverage )
    {
      count = ccsf3->InputGlyphCount;
      c     = ccsf3->InputCoverage;

      for ( n = 0; n < count; n++ )
        Free_Coverage( &c[n] );

      FREE( c );
    }

    if ( ccsf3->BacktrackCoverage )
    {
      count = ccsf3->BacktrackGlyphCount;
      c     = ccsf3->BacktrackCoverage;

      for ( n = 0; n < count; n++ )
        Free_Coverage( &c[n] );

      FREE( c );
    }
  }


  /* ChainContextSubst */

  TT_Error  Load_ChainContextSubst( TTO_ChainContextSubst*  ccs,
                                    PFace                   input )
  {
    DEFINE_LOAD_LOCALS( input->stream );


    if ( ACCESS_Frame( 2L ) )
      return error;

    ccs->SubstFormat = GET_UShort();

    FORGET_Frame();

    switch ( ccs->SubstFormat )
    {
    case 1:
      return Load_ChainContextSubst1( &ccs->ccsf.ccsf1, input );

    case 2:
      return Load_ChainContextSubst2( &ccs->ccsf.ccsf2, input );

    case 3:
      return Load_ChainContextSubst3( &ccs->ccsf.ccsf3, input );

    default:
      return TTO_Err_Invalid_GSUB_SubTable_Format;
    }

    return TT_Err_Ok;               /* never reached */
  }


  void  Free_ChainContextSubst( TTO_ChainContextSubst*  ccs )
  {
    switch ( ccs->SubstFormat )
    {
    case 1:
      Free_ChainContext1( &ccs->ccsf.ccsf1 );
      break;

    case 2:
      Free_ChainContext2( &ccs->ccsf.ccsf2 );
      break;

    case 3:
      Free_ChainContext3( &ccs->ccsf.ccsf3 );
      break;
    }
  }


  static TT_Error  Lookup_ChainContextSubst1(
                     TTO_GSUBHeader*                gsub,
                     TTO_ChainContextSubstFormat1*  ccsf1,
                     TTO_GSUB_String*               in,
                     TTO_GSUB_String*               out,
                     UShort                         flags,
                     UShort                         context_length,
                     int                            nesting_level )
  {
    UShort             index, property;
    UShort             i, j, k, num_csr, curr_pos;
    UShort             bgc, igc, lgc;
    TT_Error           error;
    UShort*            s_in;

    TTO_ChainSubRule*  csr;
    TTO_ChainSubRule   curr_csr;
    TTO_GDEFHeader*    gdef;


    gdef = gsub->gdef;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    error = Coverage_Index( &ccsf1->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    csr     = ccsf1->ChainSubRuleSet[index].ChainSubRule;
    num_csr = ccsf1->ChainSubRuleSet[index].ChainSubRuleCount;

    for ( k = 0; k < num_csr; k++ )
    {
      curr_csr = csr[k];
      bgc      = curr_csr.BacktrackGlyphCount;
      igc      = curr_csr.InputGlyphCount;
      lgc      = curr_csr.LookaheadGlyphCount;

      if ( context_length != 0xFFFF && context_length < igc )
        continue;

      /* check whether context is too long; it is a first guess only */

      if ( bgc > in->pos || in->pos + igc + lgc > in->length )
        continue;

      if ( bgc )
      {
        /* Since we don't know in advance the number of glyphs to inspect,
           we search backwards for matches in the backtrack glyph array    */

        curr_pos = 0;
        s_in     = &in->string[curr_pos];

        for ( i = bgc, j = in->pos - 1; i > 0; i--, j-- )
        {
          while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
          {
            if ( error && error != TTO_Err_Not_Covered )
              return error;

            if ( j > curr_pos )
              j--;
            else
              break;
          }

          if ( s_in[j] != curr_csr.Backtrack[i - 1] )
            break;
        }

        if ( i != 0 )
          continue;
      }

      curr_pos = in->pos;
      s_in     = &in->string[curr_pos];

      /* Start at 1 because [0] is implied */

      for ( i = 1, j = 1; i < igc; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( curr_pos + j < in->length )
            j++;
          else
            break;
        }

        if ( s_in[j] != curr_csr.Input[i - 1] )
          break;
      }

      if ( i != igc )
        continue;

      /* we are starting to check for lookahead glyphs right after the
         last context glyph                                            */

      curr_pos = j;
      s_in     = &in->string[curr_pos];

      for ( i = 0, j = 0; i < lgc; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( curr_pos + j < in->length )
            j++;
          else
            break;
        }

        if ( s_in[j] != curr_csr.Lookahead[i] )
          break;
      }

      if ( i == lgc )
        return Do_ContextSubst( gsub, igc,
                                curr_csr.SubstCount,
                                curr_csr.SubstLookupRecord,
                                in, out,
                                nesting_level );
    }

    return TTO_Err_Not_Covered;
  }


  static TT_Error  Lookup_ChainContextSubst2(
                     TTO_GSUBHeader*                gsub,
                     TTO_ChainContextSubstFormat2*  ccsf2,
                     TTO_GSUB_String*               in,
                     TTO_GSUB_String*               out,
                     UShort                         flags,
                     UShort                         context_length,
                     int                            nesting_level )
  {
    UShort                 index, property;
    TT_Error               error;
    UShort                 i, j, k, curr_pos;
    UShort                 bgc, igc, lgc;
    UShort                 known_backtrack_classes,
                           known_input_classes,
                           known_lookahead_classes;

    UShort*                backtrack_classes;
    UShort*                input_classes;
    UShort*                lookahead_classes;

    UShort*                s_in;

    UShort*                bc;
    UShort*                ic;
    UShort*                lc;

    TTO_ChainSubClassSet*  cscs;
    TTO_ChainSubClassRule  ccsr;
    TTO_GDEFHeader*        gdef;


    gdef = gsub->gdef;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    /* Note: The coverage table in format 2 doesn't give an index into
             anything.  It just lets us know whether or not we need to
             do any lookup at all.                                     */

    error = Coverage_Index( &ccsf2->Coverage, in->string[in->pos], &index );
    if ( error )
      return error;

    if ( ALLOC_ARRAY( backtrack_classes, ccsf2->MaxBacktrackLength, UShort ) )
      return error;
    known_backtrack_classes = 0;

    if ( ALLOC_ARRAY( input_classes, ccsf2->MaxInputLength, UShort ) )
      goto End3;
    known_input_classes = 1;

    if ( ALLOC_ARRAY( lookahead_classes, ccsf2->MaxLookaheadLength, UShort ) )
      goto End2;
    known_lookahead_classes = 0;

    error = Get_Class( &ccsf2->InputClassDef, in->string[in->pos],
                       &input_classes[0], NULL );
    if ( error )
      goto End1;

    cscs = &ccsf2->ChainSubClassSet[input_classes[0]];
    if ( !cscs )
    {
      error = TTO_Err_Invalid_GSUB_SubTable;
      goto End1;
    }

    for ( k = 0; k < cscs->ChainSubClassRuleCount; k++ )
    {
      ccsr = cscs->ChainSubClassRule[k];
      bgc  = ccsr.BacktrackGlyphCount;
      igc  = ccsr.InputGlyphCount;
      lgc  = ccsr.LookaheadGlyphCount;

      if ( context_length != 0xFFFF && context_length < igc )
        continue;

      /* check whether context is too long; it is a first guess only */

      if ( bgc > in->pos || in->pos + igc + lgc > in->length )
        continue;

      if ( bgc )
      {
        /* Since we don't know in advance the number of glyphs to inspect,
           we search backwards for matches in the backtrack glyph array.
           Note that `known_backtrack_classes' starts at index 0.         */

        curr_pos = 0;
        s_in     = &in->string[curr_pos];
        bc       = ccsr.Backtrack;

        for ( i = 0, j = in->pos - 1; i < bgc; i++, j-- )
        {
          while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
          {
            if ( error && error != TTO_Err_Not_Covered )
              return error;

            if ( j > curr_pos )
              j--;
            else
              break;
          }

          if ( i >= known_backtrack_classes )
          {
            /* Keeps us from having to do this for each rule */

            error = Get_Class( &ccsf2->BacktrackClassDef, s_in[j],
                               &backtrack_classes[i], NULL );
            if ( error && error != TTO_Err_Not_Covered )
              goto End1;
            known_backtrack_classes = i;
          }

          if ( bc[bgc - 1 - i] != backtrack_classes[i] )
            break;
        }

        if ( i != bgc )
          continue;
      }

      curr_pos = in->pos;
      s_in     = &in->string[curr_pos];
      ic       = ccsr.Input;

      /* Start at 1 because [0] is implied */

      for ( i = 1, j = 1; i < igc; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            goto End1;

          if ( curr_pos + j < in->length )
            j++;
          else
            break;
        }

        if ( i >= known_input_classes )
        {
          error = Get_Class( &ccsf2->InputClassDef, s_in[j],
                             &input_classes[i], NULL );
          if ( error && error != TTO_Err_Not_Covered )
            goto End1;
          known_input_classes = i;
        }

        if ( ic[i - 1] != input_classes[i] )
          break;
      }

      if ( i != igc )
        continue;

      /* we are starting to check for lookahead glyphs right after the
         last context glyph                                            */

      curr_pos = j;
      s_in     = &in->string[curr_pos];
      lc       = ccsr.Lookahead;

      for ( i = 0, j = 0; i < lgc; i++, j++ )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( curr_pos + j < in->length )
            j++;
          else
            break;
        }

        if ( i >= known_lookahead_classes )
        {
          error = Get_Class( &ccsf2->LookaheadClassDef, s_in[j],
                             &lookahead_classes[i], NULL );
          if ( error && error != TTO_Err_Not_Covered )
            goto End1;
          known_lookahead_classes = i;
        }

        if ( lc[i] != lookahead_classes[i] )
          break;
      }

      if ( i == lgc )
      {
        error = Do_ContextSubst( gsub, igc,
                                 ccsr.SubstCount,
                                 ccsr.SubstLookupRecord,
                                 in, out,
                                 nesting_level );
        goto End1;
      }
    }

    error = TTO_Err_Not_Covered;

  End1:
    FREE( lookahead_classes );

  End2:
    FREE( input_classes );

  End3:
    FREE( backtrack_classes );
    return error;
  }


  static TT_Error  Lookup_ChainContextSubst3(
                     TTO_GSUBHeader*                gsub,
                     TTO_ChainContextSubstFormat3*  ccsf3,
                     TTO_GSUB_String*               in,
                     TTO_GSUB_String*               out,
                     UShort                         flags,
                     UShort                         context_length,
                     int                            nesting_level )
  {
    UShort           index, i, j, curr_pos, property;
    UShort           bgc, igc, lgc;
    TT_Error         error;
    UShort*          s_in;

    TTO_Coverage*    bc;
    TTO_Coverage*    ic;
    TTO_Coverage*    lc;
    TTO_GDEFHeader*  gdef;


    gdef = gsub->gdef;

    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
      return error;

    bgc = ccsf3->BacktrackGlyphCount;
    igc = ccsf3->InputGlyphCount;
    lgc = ccsf3->LookaheadGlyphCount;

    if ( context_length != 0xFFFF && context_length < igc )
      return TTO_Err_Not_Covered;

    /* check whether context is too long; it is a first guess only */

    if ( bgc > in->pos || in->pos + igc + lgc > in->length )
      return TTO_Err_Not_Covered;

    if ( bgc )
    {
      /* Since we don't know in advance the number of glyphs to inspect,
         we search backwards for matches in the backtrack glyph array    */

      curr_pos = 0;
      s_in     = &in->string[curr_pos];
      bc       = ccsf3->BacktrackCoverage;

      for ( i = bgc, j = in->pos - 1; i > 0; i--, j-- )
      {
        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
        {
          if ( error && error != TTO_Err_Not_Covered )
            return error;

          if ( j > curr_pos )
            j--;
          else
            return TTO_Err_Not_Covered;
        }

        error = Coverage_Index( &bc[i - 1], s_in[j], &index );
        if ( error )
          return error;
      }
    }

    curr_pos = in->pos;
    s_in     = &in->string[curr_pos];
    ic       = ccsf3->InputCoverage;

    /* Start at 1 because [0] is implied */

    for ( i = 1, j = 1; i < igc; i++, j++ )
    {
      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
      {
        if ( error && error != TTO_Err_Not_Covered )
          return error;

        if ( curr_pos + j < in->length )
          j++;
        else
          return TTO_Err_Not_Covered;
      }

      error = Coverage_Index( &ic[i], s_in[j], &index );
      if ( error )
        return error;
    }

    /* we are starting for lookahead glyphs right after the last context
       glyph                                                             */

    curr_pos = j;
    s_in     = &in->string[curr_pos];
    lc       = ccsf3->LookaheadCoverage;

    for ( i = 0, j = 0; i < lgc; i++, j++ )
    {
      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
      {
        if ( error && error != TTO_Err_Not_Covered )
          return error;

        if ( curr_pos + j < in->length )
          j++;
        else
          return TTO_Err_Not_Covered;
      }

      error = Coverage_Index( &lc[i], s_in[j], &index );
      if ( error )
        return error;
    }

    return Do_ContextSubst( gsub, igc,
                            ccsf3->SubstCount,
                            ccsf3->SubstLookupRecord,
                            in, out,
                            nesting_level );
  }


  static TT_Error  Lookup_ChainContextSubst(
                     TTO_GSUBHeader*         gsub,
                     TTO_ChainContextSubst*  ccs,
                     TTO_GSUB_String*        in,
                     TTO_GSUB_String*        out,
                     UShort                  flags,
                     UShort                  context_length,
                     int                     nesting_level )
  {
    switch ( ccs->SubstFormat )
    {
    case 1:
      return Lookup_ChainContextSubst1( gsub, &ccs->ccsf.ccsf1, in, out,
                                        flags, context_length,
                                        nesting_level );

    case 2:
      return Lookup_ChainContextSubst2( gsub, &ccs->ccsf.ccsf2, in, out,
                                        flags, context_length,
                                        nesting_level );

    case 3:
      return Lookup_ChainContextSubst3( gsub, &ccs->ccsf.ccsf3, in, out,
                                        flags, context_length,
                                        nesting_level );

    default:
      return TTO_Err_Invalid_GSUB_SubTable_Format;
    }

    return TT_Err_Ok;               /* never reached */
  }



  /***********
   * GSUB API
   ***********/


  EXPORT_FUNC
  TT_Error  TT_GSUB_Select_Script( TTO_GSUBHeader*  gsub,
                                   TT_ULong         script_tag,
                                   TT_UShort*       script_index )
  {
    UShort             n;

    TTO_ScriptList*    sl;
    TTO_ScriptRecord*  sr;


    if ( !gsub || !script_index )
      return TT_Err_Invalid_Argument;

    sl = &gsub->ScriptList;
    sr = sl->ScriptRecord;

    for ( n = 0; n < sl->ScriptCount; n++ )
      if ( script_tag == sr[n].ScriptTag )
      {
        *script_index = n;

        return TT_Err_Ok;
      }

    return TTO_Err_Not_Covered;
  }


  EXPORT_FUNC
  TT_Error  TT_GSUB_Select_Language( TTO_GSUBHeader*  gsub,
                                     TT_ULong         language_tag,
                                     TT_UShort        script_index,
                                     TT_UShort*       language_index,
                                     TT_UShort*       req_feature_index )
  {
    UShort              n;

    TTO_ScriptList*     sl;
    TTO_ScriptRecord*   sr;
    TTO_Script*         s;
    TTO_LangSysRecord*  lsr;


    if ( !gsub || !language_index || !req_feature_index )
      return TT_Err_Invalid_Argument;

    sl = &gsub->ScriptList;
    sr = sl->ScriptRecord;

    if ( script_index >= sl->ScriptCount )
      return TT_Err_Invalid_Argument;

    s   = &sr[script_index].Script;
    lsr = s->LangSysRecord;

    for ( n = 0; n < s->LangSysCount; n++ )
      if ( language_tag == lsr[n].LangSysTag )
      {
        *language_index = n;
        *req_feature_index = lsr[n].LangSys.ReqFeatureIndex;

        return TT_Err_Ok;
      }

    return TTO_Err_Not_Covered;
  }


  /* selecting 0xFFFF for language_index asks for the values of the
     default language (DefaultLangSys)                              */

  EXPORT_FUNC
  TT_Error  TT_GSUB_Select_Feature( TTO_GSUBHeader*  gsub,
                                    TT_ULong         feature_tag,
                                    TT_UShort        script_index,
                                    TT_UShort        language_index,
                                    TT_UShort*       feature_index )
  {
    UShort              n;

    TTO_ScriptList*     sl;
    TTO_ScriptRecord*   sr;
    TTO_Script*         s;
    TTO_LangSysRecord*  lsr;
    TTO_LangSys*        ls;
    UShort*             fi;

    TTO_FeatureList*    fl;
    TTO_FeatureRecord*  fr;


    if ( !gsub || !feature_index )
      return TT_Err_Invalid_Argument;

    sl = &gsub->ScriptList;
    sr = sl->ScriptRecord;

    fl = &gsub->FeatureList;
    fr = fl->FeatureRecord;

    if ( script_index >= sl->ScriptCount )
      return TT_Err_Invalid_Argument;

    s   = &sr[script_index].Script;
    lsr = s->LangSysRecord;

    if ( language_index == 0xFFFF )
      ls = &s->DefaultLangSys;
    else
    {
      if ( language_index >= s->LangSysCount )
        return TT_Err_Invalid_Argument;

      ls = &lsr[language_index].LangSys;
    }

    fi = ls->FeatureIndex;

    for ( n = 0; n < ls->FeatureCount; n++ )
    {
      if ( fi[n] >= fl->FeatureCount )
        return TTO_Err_Invalid_GSUB_SubTable_Format;

      if ( feature_tag == fr[fi[n]].FeatureTag )
      {
        *feature_index = fi[n];

        return TT_Err_Ok;
      }
    }

    return TTO_Err_Not_Covered;
  }


  /* The next three functions return a null-terminated list */

  EXPORT_FUNC
  TT_Error  TT_GSUB_Query_Scripts( TTO_GSUBHeader*  gsub,
                                   TT_ULong**       script_tag_list )
  {
    UShort             n;
    TT_Error           error;
    ULong*             stl;

    TTO_ScriptList*    sl;
    TTO_ScriptRecord*  sr;


    if ( !gsub || !script_tag_list )
      return TT_Err_Invalid_Argument;

    sl = &gsub->ScriptList;
    sr = sl->ScriptRecord;

    if ( ALLOC_ARRAY( stl, sl->ScriptCount + 1, ULong ) )
      return error;

    for ( n = 0; n < sl->ScriptCount; n++ )
      stl[n] = sr[n].ScriptTag;
    stl[n] = 0;

    *script_tag_list = stl;

    return TT_Err_Ok;
  }


  EXPORT_FUNC
  TT_Error  TT_GSUB_Query_Languages( TTO_GSUBHeader*  gsub,
                                     TT_UShort        script_index,
                                     TT_ULong**       language_tag_list )
  {
    UShort              n;
    TT_Error            error;
    ULong*              ltl;

    TTO_ScriptList*     sl;
    TTO_ScriptRecord*   sr;
    TTO_Script*         s;
    TTO_LangSysRecord*  lsr;


    if ( !gsub || !language_tag_list )
      return TT_Err_Invalid_Argument;

    sl = &gsub->ScriptList;
    sr = sl->ScriptRecord;

    if ( script_index >= sl->ScriptCount )
      return TT_Err_Invalid_Argument;

    s   = &sr[script_index].Script;
    lsr = s->LangSysRecord;

    if ( ALLOC_ARRAY( ltl, s->LangSysCount + 1, ULong ) )
      return error;

    for ( n = 0; n < s->LangSysCount; n++ )
      ltl[n] = lsr[n].LangSysTag;
    ltl[n] = 0;

    *language_tag_list = ltl;

    return TT_Err_Ok;
  }


  /* selecting 0xFFFF for language_index asks for the values of the
     default language (DefaultLangSys)                              */

  EXPORT_FUNC
  TT_Error  TT_GSUB_Query_Features( TTO_GSUBHeader*  gsub,
                                    TT_UShort        script_index,
                                    TT_UShort        language_index,
                                    TT_ULong**       feature_tag_list )
  {
    UShort              n;
    TT_Error            error;
    ULong*              ftl;

    TTO_ScriptList*     sl;
    TTO_ScriptRecord*   sr;
    TTO_Script*         s;
    TTO_LangSysRecord*  lsr;
    TTO_LangSys*        ls;
    UShort*             fi;

    TTO_FeatureList*    fl;
    TTO_FeatureRecord*  fr;


    if ( !gsub || !feature_tag_list )
      return TT_Err_Invalid_Argument;

    sl = &gsub->ScriptList;
    sr = sl->ScriptRecord;

    fl = &gsub->FeatureList;
    fr = fl->FeatureRecord;

    if ( script_index >= sl->ScriptCount )
      return TT_Err_Invalid_Argument;

    s   = &sr[script_index].Script;
    lsr = s->LangSysRecord;

    if ( language_index == 0xFFFF )
      ls = &s->DefaultLangSys;
    else
    {
      if ( language_index >= s->LangSysCount )
        return TT_Err_Invalid_Argument;

      ls = &lsr[language_index].LangSys;
    }

    fi = ls->FeatureIndex;

    if ( ALLOC_ARRAY( ftl, ls->FeatureCount + 1, ULong ) )
      return error;

    for ( n = 0; n < ls->FeatureCount; n++ )
    {
      if ( fi[n] >= fl->FeatureCount )
      {
        FREE( ftl );
        return TTO_Err_Invalid_GSUB_SubTable_Format;
      }
      ftl[n] = fr[fi[n]].FeatureTag;
    }
    ftl[n] = 0;

    *feature_tag_list = ftl;

    return TT_Err_Ok;
  }


  /* Do an individual subtable lookup.  Returns TT_Err_Ok if substitution
     has been done, or TTO_Err_Not_Covered if not.                        */

  static TT_Error  Do_Glyph_Lookup( TTO_GSUBHeader*   gsub,
                                    UShort            lookup_index,
                                    TTO_GSUB_String*  in,
                                    TTO_GSUB_String*  out,
                                    UShort            context_length,
                                    int               nesting_level )
  {
    TT_Error     error = TT_Err_Ok;
    UShort       i, flags;
    TTO_Lookup*  lo;


    nesting_level++;

    if ( nesting_level > TTO_MAX_NESTING_LEVEL )
      return TTO_Err_Too_Many_Nested_Contexts;

    lo    = &gsub->LookupList.Lookup[lookup_index];
    flags = lo->LookupFlag;

    for ( i = 0; i < lo->SubTableCount; i++ )
    {
      switch ( lo->LookupType )
      {
      case GSUB_LOOKUP_SINGLE:
        error = Lookup_SingleSubst( &lo->SubTable[i].st.gsub.single,
                                    in, out,
                                    flags, context_length, gsub->gdef );
        break;

      case GSUB_LOOKUP_MULTIPLE:
        error = Lookup_MultipleSubst( &lo->SubTable[i].st.gsub.multiple,
                                      in, out,
                                      flags, context_length, gsub->gdef );
        break;

      case GSUB_LOOKUP_ALTERNATE:
        error = Lookup_AlternateSubst( gsub,
                                       &lo->SubTable[i].st.gsub.alternate,
                                       in, out,
                                       flags, context_length, gsub->gdef );
        break;

      case GSUB_LOOKUP_LIGATURE:
        error = Lookup_LigatureSubst( &lo->SubTable[i].st.gsub.ligature,
                                      in, out,
                                      flags, context_length, gsub->gdef );
        break;

      case GSUB_LOOKUP_CONTEXT:
        error = Lookup_ContextSubst( gsub, &lo->SubTable[i].st.gsub.context,
                                     in, out,
                                     flags, context_length, nesting_level );
        break;

      case GSUB_LOOKUP_CHAIN:
        error = Lookup_ChainContextSubst( gsub,
                                          &lo->SubTable[i].st.gsub.chain,
                                          in, out,
                                          flags, context_length,
                                          nesting_level );
        break;
      }

      /* Check whether we have a successful substitution or an error other
         than TTO_Err_Not_Covered                                          */

      if ( error != TTO_Err_Not_Covered )
        return error;
    }

    return TTO_Err_Not_Covered;
  }


  /* apply one lookup to the input string object */

  static TT_Error  Do_String_Lookup( TTO_GSUBHeader*   gsub,
                                     UShort            lookup_index,
                                     TTO_GSUB_String*  in,
                                     TTO_GSUB_String*  out )
  {
    TT_Error  error = TTO_Err_Not_Covered;

    UShort*  properties = gsub->LookupList.Properties;
    UShort*  p_in       = in->properties;
    UShort*  s_in       = in->string;

    int      nesting_level = 0;


    while ( in->pos < in->length )
    {
      if ( ~p_in[in->pos] & properties[lookup_index] )
      {
        /* 0xFFFF indicates that we don't have a context length yet */
        error = Do_Glyph_Lookup( gsub, lookup_index, in, out,
                                 0xFFFF, nesting_level );
        if ( error && error != TTO_Err_Not_Covered )
          return error;
      }
      else
        error = TTO_Err_Not_Covered;

      if ( error == TTO_Err_Not_Covered ) 
        if ( ADD_String( in, 1, out, 1, &s_in[in->pos] ) )
          return error;
    }

    return error;
  }


  EXPORT_FUNC
  TT_Error  TT_GSUB_Add_Feature( TTO_GSUBHeader*  gsub,
                                 TT_UShort        feature_index,
                                 TT_UShort        property )
  {
    UShort       i;

    TTO_Feature  feature;
    UShort*      properties;
    UShort*      index;


    if ( !gsub ||
         feature_index >= gsub->FeatureList.FeatureCount )
      return TT_Err_Invalid_Argument;

    properties = gsub->LookupList.Properties;

    feature = gsub->FeatureList.FeatureRecord[feature_index].Feature;
    index   = feature.LookupListIndex;

    for ( i = 0; i < feature.LookupListCount; i++ )
      properties[index[i]] |= property;

    return TT_Err_Ok;
  }


  EXPORT_FUNC
  TT_Error  TT_GSUB_Clear_Features( TTO_GSUBHeader*  gsub )
  {
    UShort   i;

    UShort*  properties;


    if ( !gsub )
      return TT_Err_Invalid_Argument;

    properties = gsub->LookupList.Properties;

    for ( i = 0; i < gsub->LookupList.LookupCount; i++ )
      properties[i] = 0;

    return TT_Err_Ok;
  }


  EXPORT_FUNC
  TT_Error  TT_GSUB_Register_Alternate_Function( TTO_GSUBHeader*  gsub,
                                                 TTO_AltFunction  alt,
                                                 void*            data )
  {
    if ( !gsub )
      return TT_Err_Invalid_Argument;

    gsub->alt  = alt;
    gsub->data = data;

    return TT_Err_Ok;
  }


  EXPORT_FUNC
  TT_Error  TT_GSUB_Apply_String( TTO_GSUBHeader*   gsub,
                                  TTO_GSUB_String*  in,
                                  TTO_GSUB_String*  out )
  {
    TT_Error          error = TTO_Err_Not_Covered;
    UShort            j;

    TTO_GSUB_String   tmp1;
    TTO_GSUB_String*  ptmp1;
    TTO_GSUB_String   tmp2;
    TTO_GSUB_String*  ptmp2;
    TTO_GSUB_String*  t;

    UShort*           properties;


    if ( !gsub ||
         !in || !out || in->length == 0 || in->pos >= in->length )
      return TT_Err_Invalid_Argument;

    properties = gsub->LookupList.Properties;

    tmp1.length    = in->length;
    tmp1.allocated = in->length;
    tmp1.pos       = in->pos;

    if ( ALLOC_ARRAY( tmp1.string, tmp1.length, UShort ) )
      return error;
    MEM_Copy( tmp1.string, in->string, in->length * sizeof ( UShort ) );

    /* make sure that we always have a `properties' array in the string
       object                                                           */

    if ( ALLOC_ARRAY( tmp1.properties, tmp1.length, UShort ) )
      return error;
    if ( in->properties )
      MEM_Copy( tmp1.properties, in->properties,
                in->length * sizeof( UShort ) );

    tmp2.allocated  = 0;
    tmp2.pos        = 0;
    tmp2.string     = NULL;
    tmp2.properties = NULL;

    ptmp1 = &tmp1;
    ptmp2 = &tmp2;

    for ( j = 0; j < gsub->LookupList.LookupCount; j++ )
      if ( properties[j] )
      {
        error = Do_String_Lookup( gsub, j, ptmp1, ptmp2 );
        if ( error && error != TTO_Err_Not_Covered )
          return error;

        /* flipping `in' and `out', preparing for the next loop */

        ptmp1->pos    = in->pos;
        ptmp2->length = ptmp2->pos;
        ptmp2->pos    = in->pos;

        t     = ptmp2;
        ptmp2 = ptmp1;
        ptmp1 = t;
      }

    out->length    = ptmp1->length;
    out->pos       = 0;
    out->allocated = ptmp1->allocated;
    out->string    = ptmp1->string;

    if ( in->properties )
      out->properties = ptmp1->properties;
    else
    {
      free( ptmp1->properties );
      out->properties = NULL;
    }

    free( ptmp2->string );
    free( ptmp2->properties );

    return error;
  }


/* END */