misc/libfreetype/src/gxvalid/gxvcommn.c
author koda
Sat, 06 Aug 2011 07:09:30 +0200
changeset 5505 a55aab592950
parent 5172 88f2e05288ba
permissions -rw-r--r--
Ditch the renderer system in sdl1.3 and use the 'old fashioned' sdl/opengl context. This gives us more flexibility and less problem in receiving video events (expecially on mobile platform) as well as not having to care to reset the gl context every time sdl interferes. This is a major sdl1.3 update so it should be tested with care (working great on ios)

/***************************************************************************/
/*                                                                         */
/*  gxvcommn.c                                                             */
/*                                                                         */
/*    TrueTypeGX/AAT common tables validation (body).                      */
/*                                                                         */
/*  Copyright 2004, 2005, 2009, 2010                                       */
/*  by suzuki toshiya, Masatake YAMATO, Red Hat K.K.,                      */
/*  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.                                        */
/*                                                                         */
/***************************************************************************/

/***************************************************************************/
/*                                                                         */
/* gxvalid is derived from both gxlayout module and otvalid module.        */
/* Development of gxlayout is supported by the Information-technology      */
/* Promotion Agency(IPA), Japan.                                           */
/*                                                                         */
/***************************************************************************/


#include "gxvcommn.h"


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


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                       16bit offset sorter                     *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  static int
  gxv_compare_ushort_offset( FT_UShort*  a,
                             FT_UShort*  b )
  {
    if ( *a < *b )
      return -1;
    else if ( *a > *b )
      return 1;
    else
      return 0;
  }


  FT_LOCAL_DEF( void )
  gxv_set_length_by_ushort_offset( FT_UShort*     offset,
                                   FT_UShort**    length,
                                   FT_UShort*     buff,
                                   FT_UInt        nmemb,
                                   FT_UShort      limit,
                                   GXV_Validator  valid )
  {
    FT_UInt  i;


    for ( i = 0; i < nmemb; i++ )
      *(length[i]) = 0;

    for ( i = 0; i < nmemb; i++ )
      buff[i] = offset[i];
    buff[nmemb] = limit;

    ft_qsort( buff, ( nmemb + 1 ), sizeof ( FT_UShort ),
              ( int(*)(const void*, const void*) )gxv_compare_ushort_offset );

    if ( buff[nmemb] > limit )
      FT_INVALID_OFFSET;

    for ( i = 0; i < nmemb; i++ )
    {
      FT_UInt  j;


      for ( j = 0; j < nmemb; j++ )
        if ( buff[j] == offset[i] )
          break;

      if ( j == nmemb )
        FT_INVALID_OFFSET;

      *(length[i]) = (FT_UShort)( buff[j + 1] - buff[j] );

      if ( 0 != offset[i] && 0 == *(length[i]) )
        FT_INVALID_OFFSET;
    }
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                       32bit offset sorter                     *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  static int
  gxv_compare_ulong_offset( FT_ULong*  a,
                            FT_ULong*  b )
  {
    if ( *a < *b )
      return -1;
    else if ( *a > *b )
      return 1;
    else
      return 0;
  }


  FT_LOCAL_DEF( void )
  gxv_set_length_by_ulong_offset( FT_ULong*      offset,
                                  FT_ULong**     length,
                                  FT_ULong*      buff,
                                  FT_UInt        nmemb,
                                  FT_ULong       limit,
                                  GXV_Validator  valid)
  {
    FT_UInt  i;


    for ( i = 0; i < nmemb; i++ )
      *(length[i]) = 0;

    for ( i = 0; i < nmemb; i++ )
      buff[i] = offset[i];
    buff[nmemb] = limit;

    ft_qsort( buff, ( nmemb + 1 ), sizeof ( FT_ULong ),
              ( int(*)(const void*, const void*) )gxv_compare_ulong_offset );

    if ( buff[nmemb] > limit )
      FT_INVALID_OFFSET;

    for ( i = 0; i < nmemb; i++ )
    {
      FT_UInt  j;


      for ( j = 0; j < nmemb; j++ )
        if ( buff[j] == offset[i] )
          break;

      if ( j == nmemb )
        FT_INVALID_OFFSET;

      *(length[i]) = buff[j + 1] - buff[j];

      if ( 0 != offset[i] && 0 == *(length[i]) )
        FT_INVALID_OFFSET;
    }
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****               scan value array and get min & max              *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  FT_LOCAL_DEF( void )
  gxv_array_getlimits_byte( FT_Bytes       table,
                            FT_Bytes       limit,
                            FT_Byte*       min,
                            FT_Byte*       max,
                            GXV_Validator  valid )
  {
    FT_Bytes  p = table;


    *min = 0xFF;
    *max = 0x00;

    while ( p < limit )
    {
      FT_Byte  val;


      GXV_LIMIT_CHECK( 1 );
      val = FT_NEXT_BYTE( p );

      *min = (FT_Byte)FT_MIN( *min, val );
      *max = (FT_Byte)FT_MAX( *max, val );
    }

    valid->subtable_length = p - table;
  }


  FT_LOCAL_DEF( void )
  gxv_array_getlimits_ushort( FT_Bytes       table,
                              FT_Bytes       limit,
                              FT_UShort*     min,
                              FT_UShort*     max,
                              GXV_Validator  valid )
  {
    FT_Bytes  p = table;


    *min = 0xFFFFU;
    *max = 0x0000;

    while ( p < limit )
    {
      FT_UShort  val;


      GXV_LIMIT_CHECK( 2 );
      val = FT_NEXT_USHORT( p );

      *min = (FT_Byte)FT_MIN( *min, val );
      *max = (FT_Byte)FT_MAX( *max, val );
    }

    valid->subtable_length = p - table;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                       BINSEARCHHEADER                         *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  typedef struct  GXV_BinSrchHeader_
  {
    FT_UShort  unitSize;
    FT_UShort  nUnits;
    FT_UShort  searchRange;
    FT_UShort  entrySelector;
    FT_UShort  rangeShift;

  } GXV_BinSrchHeader;


  static void
  gxv_BinSrchHeader_check_consistency( GXV_BinSrchHeader*  binSrchHeader,
                                       GXV_Validator       valid )
  {
    FT_UShort  searchRange;
    FT_UShort  entrySelector;
    FT_UShort  rangeShift;


    if ( binSrchHeader->unitSize == 0 )
      FT_INVALID_DATA;

    if ( binSrchHeader->nUnits == 0 )
    {
      if ( binSrchHeader->searchRange   == 0 &&
           binSrchHeader->entrySelector == 0 &&
           binSrchHeader->rangeShift    == 0 )
        return;
      else
        FT_INVALID_DATA;
    }

    for ( searchRange = 1, entrySelector = 1;
          ( searchRange * 2 ) <= binSrchHeader->nUnits &&
            searchRange < 0x8000U;
          searchRange *= 2, entrySelector++ )
      ;

    entrySelector--;
    searchRange = (FT_UShort)( searchRange * binSrchHeader->unitSize );
    rangeShift  = (FT_UShort)( binSrchHeader->nUnits * binSrchHeader->unitSize
                               - searchRange );

    if ( searchRange   != binSrchHeader->searchRange   ||
         entrySelector != binSrchHeader->entrySelector ||
         rangeShift    != binSrchHeader->rangeShift    )
    {
      GXV_TRACE(( "Inconsistency found in BinSrchHeader\n" ));
      GXV_TRACE(( "originally: unitSize=%d, nUnits=%d, "
                  "searchRange=%d, entrySelector=%d, "
                  "rangeShift=%d\n",
                  binSrchHeader->unitSize, binSrchHeader->nUnits,
                  binSrchHeader->searchRange, binSrchHeader->entrySelector,
                  binSrchHeader->rangeShift ));
      GXV_TRACE(( "calculated: unitSize=%d, nUnits=%d, "
                  "searchRange=%d, entrySelector=%d, "
                  "rangeShift=%d\n",
                  binSrchHeader->unitSize, binSrchHeader->nUnits,
                  searchRange, entrySelector, rangeShift ));

      if ( valid->root->level >= FT_VALIDATE_PARANOID )
        FT_INVALID_DATA;
    }
  }


  /*
   * parser & validator of BinSrchHeader
   * which is used in LookupTable format 2, 4, 6.
   *
   * Essential parameters (unitSize, nUnits) are returned by
   * given pointer, others (searchRange, entrySelector, rangeShift)
   * can be calculated by essential parameters, so they are just
   * validated and discarded.
   *
   * However, wrong values in searchRange, entrySelector, rangeShift
   * won't cause fatal errors, because these parameters might be
   * only used in old m68k font driver in MacOS.
   *   -- suzuki toshiya <mpsuzuki@hiroshima-u.ac.jp>
   */

  FT_LOCAL_DEF( void )
  gxv_BinSrchHeader_validate( FT_Bytes       table,
                              FT_Bytes       limit,
                              FT_UShort*     unitSize_p,
                              FT_UShort*     nUnits_p,
                              GXV_Validator  valid )
  {
    FT_Bytes           p = table;
    GXV_BinSrchHeader  binSrchHeader;


    GXV_NAME_ENTER( "BinSrchHeader validate" );

    if ( *unitSize_p == 0 )
    {
      GXV_LIMIT_CHECK( 2 );
      binSrchHeader.unitSize =  FT_NEXT_USHORT( p );
    }
    else
      binSrchHeader.unitSize = *unitSize_p;

    if ( *nUnits_p == 0 )
    {
      GXV_LIMIT_CHECK( 2 );
      binSrchHeader.nUnits = FT_NEXT_USHORT( p );
    }
    else
      binSrchHeader.nUnits = *nUnits_p;

    GXV_LIMIT_CHECK( 2 + 2 + 2 );
    binSrchHeader.searchRange   = FT_NEXT_USHORT( p );
    binSrchHeader.entrySelector = FT_NEXT_USHORT( p );
    binSrchHeader.rangeShift    = FT_NEXT_USHORT( p );
    GXV_TRACE(( "nUnits %d\n", binSrchHeader.nUnits ));

    gxv_BinSrchHeader_check_consistency( &binSrchHeader, valid );

    if ( *unitSize_p == 0 )
      *unitSize_p = binSrchHeader.unitSize;

    if ( *nUnits_p == 0 )
      *nUnits_p = binSrchHeader.nUnits;

    valid->subtable_length = p - table;
    GXV_EXIT;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                         LOOKUP TABLE                          *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

#define GXV_LOOKUP_VALUE_LOAD( P, SIGNSPEC )                   \
          ( P += 2, gxv_lookup_value_load( P - 2, SIGNSPEC ) )

  static GXV_LookupValueDesc
  gxv_lookup_value_load( FT_Bytes  p,
                         int       signspec )
  {
    GXV_LookupValueDesc  v;


    if ( signspec == GXV_LOOKUPVALUE_UNSIGNED )
      v.u = FT_NEXT_USHORT( p );
    else
      v.s = FT_NEXT_SHORT( p );

    return v;
  }


#define GXV_UNITSIZE_VALIDATE( FORMAT, UNITSIZE, NUNITS, CORRECTSIZE ) \
          FT_BEGIN_STMNT                                               \
            if ( UNITSIZE != CORRECTSIZE )                             \
            {                                                          \
              FT_ERROR(( "unitSize=%d differs from"                    \
                         " expected unitSize=%d"                       \
                         " in LookupTable %s\n",                       \
                          UNITSIZE, CORRECTSIZE, FORMAT ));            \
              if ( UNITSIZE != 0 && NUNITS != 0 )                      \
              {                                                        \
                FT_ERROR(( " cannot validate anymore\n" ));            \
                FT_INVALID_FORMAT;                                     \
              }                                                        \
              else                                                     \
                FT_ERROR(( " forcibly continues\n" ));                 \
            }                                                          \
          FT_END_STMNT


  /* ================= Simple Array Format 0 Lookup Table ================ */
  static void
  gxv_LookupTable_fmt0_validate( FT_Bytes       table,
                                 FT_Bytes       limit,
                                 GXV_Validator  valid )
  {
    FT_Bytes   p = table;
    FT_UShort  i;

    GXV_LookupValueDesc  value;


    GXV_NAME_ENTER( "LookupTable format 0" );

    GXV_LIMIT_CHECK( 2 * valid->face->num_glyphs );

    for ( i = 0; i < valid->face->num_glyphs; i++ )
    {
      GXV_LIMIT_CHECK( 2 );
      if ( p + 2 >= limit )     /* some fonts have too-short fmt0 array */
      {
        GXV_TRACE(( "too short, glyphs %d - %d are missing\n",
                    i, valid->face->num_glyphs ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_GLYPH_ID;
        break;
      }

      value = GXV_LOOKUP_VALUE_LOAD( p, valid->lookupval_sign );
      valid->lookupval_func( i, &value, valid );
    }

    valid->subtable_length = p - table;
    GXV_EXIT;
  }


  /* ================= Segment Single Format 2 Loolup Table ============== */
  /*
   * Apple spec says:
   *
   *   To guarantee that a binary search terminates, you must include one or
   *   more special `end of search table' values at the end of the data to
   *   be searched.  The number of termination values that need to be
   *   included is table-specific.  The value that indicates binary search
   *   termination is 0xFFFF.
   *
   * The problem is that nUnits does not include this end-marker.  It's
   * quite difficult to discriminate whether the following 0xFFFF comes from
   * the end-marker or some next data.
   *
   *   -- suzuki toshiya <mpsuzuki@hiroshima-u.ac.jp>
   */
  static void
  gxv_LookupTable_fmt2_skip_endmarkers( FT_Bytes       table,
                                        FT_UShort      unitSize,
                                        GXV_Validator  valid )
  {
    FT_Bytes  p = table;


    while ( ( p + 4 ) < valid->root->limit )
    {
      if ( p[0] != 0xFF || p[1] != 0xFF || /* lastGlyph */
           p[2] != 0xFF || p[3] != 0xFF )  /* firstGlyph */
        break;
      p += unitSize;
    }

    valid->subtable_length = p - table;
  }


  static void
  gxv_LookupTable_fmt2_validate( FT_Bytes       table,
                                 FT_Bytes       limit,
                                 GXV_Validator  valid )
  {
    FT_Bytes             p = table;
    FT_UShort            gid;

    FT_UShort            unitSize;
    FT_UShort            nUnits;
    FT_UShort            unit;
    FT_UShort            lastGlyph;
    FT_UShort            firstGlyph;
    GXV_LookupValueDesc  value;


    GXV_NAME_ENTER( "LookupTable format 2" );

    unitSize = nUnits = 0;
    gxv_BinSrchHeader_validate( p, limit, &unitSize, &nUnits, valid );
    p += valid->subtable_length;

    GXV_UNITSIZE_VALIDATE( "format2", unitSize, nUnits, 6 );

    for ( unit = 0, gid = 0; unit < nUnits; unit++ )
    {
      GXV_LIMIT_CHECK( 2 + 2 + 2 );
      lastGlyph  = FT_NEXT_USHORT( p );
      firstGlyph = FT_NEXT_USHORT( p );
      value      = GXV_LOOKUP_VALUE_LOAD( p, valid->lookupval_sign );

      gxv_glyphid_validate( firstGlyph, valid );
      gxv_glyphid_validate( lastGlyph, valid );

      if ( lastGlyph < gid )
      {
        GXV_TRACE(( "reverse ordered segment specification:"
                    " lastGlyph[%d]=%d < lastGlyph[%d]=%d\n",
                    unit, lastGlyph, unit - 1 , gid ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_GLYPH_ID;
      }

      if ( lastGlyph < firstGlyph )
      {
        GXV_TRACE(( "reverse ordered range specification at unit %d:",
                    " lastGlyph %d < firstGlyph %d ",
                    unit, lastGlyph, firstGlyph ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_GLYPH_ID;

        if ( valid->root->level == FT_VALIDATE_TIGHT )
          continue;     /* ftxvalidator silently skips such an entry */

        FT_TRACE4(( "continuing with exchanged values\n" ));
        gid        = firstGlyph;
        firstGlyph = lastGlyph;
        lastGlyph  = gid;
      }

      for ( gid = firstGlyph; gid <= lastGlyph; gid++ )
        valid->lookupval_func( gid, &value, valid );
    }

    gxv_LookupTable_fmt2_skip_endmarkers( p, unitSize, valid );
    p += valid->subtable_length;

    valid->subtable_length = p - table;
    GXV_EXIT;
  }


  /* ================= Segment Array Format 4 Lookup Table =============== */
  static void
  gxv_LookupTable_fmt4_validate( FT_Bytes       table,
                                 FT_Bytes       limit,
                                 GXV_Validator  valid )
  {
    FT_Bytes             p = table;
    FT_UShort            unit;
    FT_UShort            gid;

    FT_UShort            unitSize;
    FT_UShort            nUnits;
    FT_UShort            lastGlyph;
    FT_UShort            firstGlyph;
    GXV_LookupValueDesc  base_value;
    GXV_LookupValueDesc  value;


    GXV_NAME_ENTER( "LookupTable format 4" );

    unitSize = nUnits = 0;
    gxv_BinSrchHeader_validate( p, limit, &unitSize, &nUnits, valid );
    p += valid->subtable_length;

    GXV_UNITSIZE_VALIDATE( "format4", unitSize, nUnits, 6 );

    for ( unit = 0, gid = 0; unit < nUnits; unit++ )
    {
      GXV_LIMIT_CHECK( 2 + 2 );
      lastGlyph  = FT_NEXT_USHORT( p );
      firstGlyph = FT_NEXT_USHORT( p );

      gxv_glyphid_validate( firstGlyph, valid );
      gxv_glyphid_validate( lastGlyph, valid );

      if ( lastGlyph < gid )
      {
        GXV_TRACE(( "reverse ordered segment specification:"
                    " lastGlyph[%d]=%d < lastGlyph[%d]=%d\n",
                    unit, lastGlyph, unit - 1 , gid ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_GLYPH_ID;
      }

      if ( lastGlyph < firstGlyph )
      {
        GXV_TRACE(( "reverse ordered range specification at unit %d:",
                    " lastGlyph %d < firstGlyph %d ",
                    unit, lastGlyph, firstGlyph ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_GLYPH_ID;

        if ( valid->root->level == FT_VALIDATE_TIGHT )
          continue; /* ftxvalidator silently skips such an entry */

        FT_TRACE4(( "continuing with exchanged values\n" ));
        gid        = firstGlyph;
        firstGlyph = lastGlyph;
        lastGlyph  = gid;
      }

      GXV_LIMIT_CHECK( 2 );
      base_value = GXV_LOOKUP_VALUE_LOAD( p, GXV_LOOKUPVALUE_UNSIGNED );

      for ( gid = firstGlyph; gid <= lastGlyph; gid++ )
      {
        value = valid->lookupfmt4_trans( (FT_UShort)( gid - firstGlyph ),
                                         &base_value,
                                         limit,
                                         valid );

        valid->lookupval_func( gid, &value, valid );
      }
    }

    gxv_LookupTable_fmt2_skip_endmarkers( p, unitSize, valid );
    p += valid->subtable_length;

    valid->subtable_length = p - table;
    GXV_EXIT;
  }


  /* ================= Segment Table Format 6 Lookup Table =============== */
  static void
  gxv_LookupTable_fmt6_skip_endmarkers( FT_Bytes       table,
                                        FT_UShort      unitSize,
                                        GXV_Validator  valid )
  {
    FT_Bytes  p = table;


    while ( p < valid->root->limit )
    {
      if ( p[0] != 0xFF || p[1] != 0xFF )
        break;
      p += unitSize;
    }

    valid->subtable_length = p - table;
  }


  static void
  gxv_LookupTable_fmt6_validate( FT_Bytes       table,
                                 FT_Bytes       limit,
                                 GXV_Validator  valid )
  {
    FT_Bytes             p = table;
    FT_UShort            unit;
    FT_UShort            prev_glyph;

    FT_UShort            unitSize;
    FT_UShort            nUnits;
    FT_UShort            glyph;
    GXV_LookupValueDesc  value;


    GXV_NAME_ENTER( "LookupTable format 6" );

    unitSize = nUnits = 0;
    gxv_BinSrchHeader_validate( p, limit, &unitSize, &nUnits, valid );
    p += valid->subtable_length;

    GXV_UNITSIZE_VALIDATE( "format6", unitSize, nUnits, 4 );

    for ( unit = 0, prev_glyph = 0; unit < nUnits; unit++ )
    {
      GXV_LIMIT_CHECK( 2 + 2 );
      glyph = FT_NEXT_USHORT( p );
      value = GXV_LOOKUP_VALUE_LOAD( p, valid->lookupval_sign );

      if ( gxv_glyphid_validate( glyph, valid ) )
        GXV_TRACE(( " endmarker found within defined range"
                    " (entry %d < nUnits=%d)\n",
                    unit, nUnits ));

      if ( prev_glyph > glyph )
      {
        GXV_TRACE(( "current gid 0x%04x < previous gid 0x%04x\n",
                    glyph, prev_glyph ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_GLYPH_ID;
      }
      prev_glyph = glyph;

      valid->lookupval_func( glyph, &value, valid );
    }

    gxv_LookupTable_fmt6_skip_endmarkers( p, unitSize, valid );
    p += valid->subtable_length;

    valid->subtable_length = p - table;
    GXV_EXIT;
  }


  /* ================= Trimmed Array Format 8 Lookup Table =============== */
  static void
  gxv_LookupTable_fmt8_validate( FT_Bytes       table,
                                 FT_Bytes       limit,
                                 GXV_Validator  valid )
  {
    FT_Bytes              p = table;
    FT_UShort             i;

    GXV_LookupValueDesc   value;
    FT_UShort             firstGlyph;
    FT_UShort             glyphCount;


    GXV_NAME_ENTER( "LookupTable format 8" );

    /* firstGlyph + glyphCount */
    GXV_LIMIT_CHECK( 2 + 2 );
    firstGlyph = FT_NEXT_USHORT( p );
    glyphCount = FT_NEXT_USHORT( p );

    gxv_glyphid_validate( firstGlyph, valid );
    gxv_glyphid_validate( (FT_UShort)( firstGlyph + glyphCount ), valid );

    /* valueArray */
    for ( i = 0; i < glyphCount; i++ )
    {
      GXV_LIMIT_CHECK( 2 );
      value = GXV_LOOKUP_VALUE_LOAD( p, valid->lookupval_sign );
      valid->lookupval_func( (FT_UShort)( firstGlyph + i ), &value, valid );
    }

    valid->subtable_length = p - table;
    GXV_EXIT;
  }


  FT_LOCAL_DEF( void )
  gxv_LookupTable_validate( FT_Bytes       table,
                            FT_Bytes       limit,
                            GXV_Validator  valid )
  {
    FT_Bytes   p = table;
    FT_UShort  format;

    GXV_Validate_Func  fmt_funcs_table[] =
    {
      gxv_LookupTable_fmt0_validate, /* 0 */
      NULL,                          /* 1 */
      gxv_LookupTable_fmt2_validate, /* 2 */
      NULL,                          /* 3 */
      gxv_LookupTable_fmt4_validate, /* 4 */
      NULL,                          /* 5 */
      gxv_LookupTable_fmt6_validate, /* 6 */
      NULL,                          /* 7 */
      gxv_LookupTable_fmt8_validate, /* 8 */
    };

    GXV_Validate_Func  func;


    GXV_NAME_ENTER( "LookupTable" );

    /* lookuptbl_head may be used in fmt4 transit function. */
    valid->lookuptbl_head = table;

    /* format */
    GXV_LIMIT_CHECK( 2 );
    format = FT_NEXT_USHORT( p );
    GXV_TRACE(( " (format %d)\n", format ));

    if ( format > 8 )
      FT_INVALID_FORMAT;

    func = fmt_funcs_table[format];
    if ( func == NULL )
      FT_INVALID_FORMAT;

    func( p, limit, valid );
    p += valid->subtable_length;

    valid->subtable_length = p - table;

    GXV_EXIT;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                          Glyph ID                             *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL_DEF( FT_Int )
  gxv_glyphid_validate( FT_UShort      gid,
                        GXV_Validator  valid )
  {
    FT_Face  face;


    if ( gid == 0xFFFFU )
    {
      GXV_EXIT;
      return 1;
    }

    face = valid->face;
    if ( face->num_glyphs < gid )
    {
      GXV_TRACE(( " gxv_glyphid_check() gid overflow: num_glyphs %d < %d\n",
                  face->num_glyphs, gid ));
      if ( valid->root->level >= FT_VALIDATE_PARANOID )
        FT_INVALID_GLYPH_ID;
    }

    return 0;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                        CONTROL POINT                          *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL_DEF( void )
  gxv_ctlPoint_validate( FT_UShort      gid,
                         FT_Short       ctl_point,
                         GXV_Validator  valid )
  {
    FT_Face       face;
    FT_Error      error;

    FT_GlyphSlot  glyph;
    FT_Outline    outline;
    short         n_points;


    face = valid->face;

    error = FT_Load_Glyph( face,
                           gid,
                           FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM );
    if ( error )
      FT_INVALID_GLYPH_ID;

    glyph    = face->glyph;
    outline  = glyph->outline;
    n_points = outline.n_points;


    if ( !( ctl_point < n_points ) )
      FT_INVALID_DATA;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                          SFNT NAME                            *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL_DEF( void )
  gxv_sfntName_validate( FT_UShort      name_index,
                         FT_UShort      min_index,
                         FT_UShort      max_index,
                         GXV_Validator  valid )
  {
    FT_SfntName  name;
    FT_UInt      i;
    FT_UInt      nnames;


    GXV_NAME_ENTER( "sfntName" );

    if ( name_index < min_index || max_index < name_index )
      FT_INVALID_FORMAT;

    nnames = FT_Get_Sfnt_Name_Count( valid->face );
    for ( i = 0; i < nnames; i++ )
    {
      if ( FT_Get_Sfnt_Name( valid->face, i, &name ) != GXV_Err_Ok )
        continue ;

      if ( name.name_id == name_index )
        goto Out;
    }

    GXV_TRACE(( "  nameIndex = %d (UNTITLED)\n", name_index ));
    FT_INVALID_DATA;
    goto Exit;  /* make compiler happy */

  Out:
    FT_TRACE1(( "  nameIndex = %d (", name_index ));
    GXV_TRACE_HEXDUMP_SFNTNAME( name );
    FT_TRACE1(( ")\n" ));

  Exit:
    GXV_EXIT;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                          STATE TABLE                          *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  /* -------------------------- Class Table --------------------------- */

  /*
   * highestClass specifies how many classes are defined in this
   * Class Subtable.  Apple spec does not mention whether undefined
   * holes in the class (e.g.: 0-3 are predefined, 4 is unused, 5 is used)
   * are permitted.  At present, holes in a defined class are not checked.
   *   -- suzuki toshiya <mpsuzuki@hiroshima-u.ac.jp>
   */

  static void
  gxv_ClassTable_validate( FT_Bytes       table,
                           FT_UShort*     length_p,
                           FT_UShort      stateSize,
                           FT_Byte*       maxClassID_p,
                           GXV_Validator  valid )
  {
    FT_Bytes   p     = table;
    FT_Bytes   limit = table + *length_p;
    FT_UShort  firstGlyph;
    FT_UShort  nGlyphs;


    GXV_NAME_ENTER( "ClassTable" );

    *maxClassID_p = 3;  /* Classes 0, 2, and 3 are predefined */

    GXV_LIMIT_CHECK( 2 + 2 );
    firstGlyph = FT_NEXT_USHORT( p );
    nGlyphs    = FT_NEXT_USHORT( p );

    GXV_TRACE(( " (firstGlyph = %d, nGlyphs = %d)\n", firstGlyph, nGlyphs ));

    if ( !nGlyphs )
      goto Out;

    gxv_glyphid_validate( (FT_UShort)( firstGlyph + nGlyphs ), valid );

    {
      FT_Byte    nGlyphInClass[256];
      FT_Byte    classID;
      FT_UShort  i;


      ft_memset( nGlyphInClass, 0, 256 );


      for ( i = 0; i < nGlyphs; i++ )
      {
        GXV_LIMIT_CHECK( 1 );
        classID = FT_NEXT_BYTE( p );
        switch ( classID )
        {
          /* following classes should not appear in class array */
        case 0:             /* end of text */
        case 2:             /* out of bounds */
        case 3:             /* end of line */
          FT_INVALID_DATA;
          break;

        case 1:             /* out of bounds */
        default:            /* user-defined: 4 - ( stateSize - 1 ) */
          if ( classID >= stateSize )
            FT_INVALID_DATA;   /* assign glyph to undefined state */

          nGlyphInClass[classID]++;
          break;
        }
      }
      *length_p = (FT_UShort)( p - table );

      /* scan max ClassID in use */
      for ( i = 0; i < stateSize; i++ )
        if ( ( 3 < i ) && ( nGlyphInClass[i] > 0 ) )
          *maxClassID_p = (FT_Byte)i;  /* XXX: Check Range? */
    }

  Out:
    GXV_TRACE(( "Declared stateSize=0x%02x, Used maxClassID=0x%02x\n",
                stateSize, *maxClassID_p ));
    GXV_EXIT;
  }


  /* --------------------------- State Array ----------------------------- */

  static void
  gxv_StateArray_validate( FT_Bytes       table,
                           FT_UShort*     length_p,
                           FT_Byte        maxClassID,
                           FT_UShort      stateSize,
                           FT_Byte*       maxState_p,
                           FT_Byte*       maxEntry_p,
                           GXV_Validator  valid )
  {
    FT_Bytes  p = table;
    FT_Bytes  limit = table + *length_p;
    FT_Byte   clazz;
    FT_Byte   entry;

    FT_UNUSED( stateSize ); /* for the non-debugging case */


    GXV_NAME_ENTER( "StateArray" );

    GXV_TRACE(( "parse %d bytes by stateSize=%d maxClassID=%d\n",
                (int)(*length_p), stateSize, (int)(maxClassID) ));

    /*
     * 2 states are predefined and must be described in StateArray:
     * state 0 (start of text), 1 (start of line)
     */
    GXV_LIMIT_CHECK( ( 1 + maxClassID ) * 2 );

    *maxState_p = 0;
    *maxEntry_p = 0;

    /* read if enough to read another state */
    while ( p + ( 1 + maxClassID ) <= limit )
    {
      (*maxState_p)++;
      for ( clazz = 0; clazz <= maxClassID; clazz++ )
      {
        entry = FT_NEXT_BYTE( p );
        *maxEntry_p = (FT_Byte)FT_MAX( *maxEntry_p, entry );
      }
    }
    GXV_TRACE(( "parsed: maxState=%d, maxEntry=%d\n",
                *maxState_p, *maxEntry_p ));

    *length_p = (FT_UShort)( p - table );

    GXV_EXIT;
  }


  /* --------------------------- Entry Table ----------------------------- */

  static void
  gxv_EntryTable_validate( FT_Bytes       table,
                           FT_UShort*     length_p,
                           FT_Byte        maxEntry,
                           FT_UShort      stateArray,
                           FT_UShort      stateArray_length,
                           FT_Byte        maxClassID,
                           FT_Bytes       statetable_table,
                           FT_Bytes       statetable_limit,
                           GXV_Validator  valid )
  {
    FT_Bytes  p     = table;
    FT_Bytes  limit = table + *length_p;
    FT_Byte   entry;
    FT_Byte   state;
    FT_Int    entrySize = 2 + 2 + GXV_GLYPHOFFSET_SIZE( statetable );

    GXV_XStateTable_GlyphOffsetDesc  glyphOffset;


    GXV_NAME_ENTER( "EntryTable" );

    GXV_TRACE(( "maxEntry=%d entrySize=%d\n", maxEntry, entrySize ));

    if ( ( maxEntry + 1 ) * entrySize > *length_p )
    {
      if ( valid->root->level >= FT_VALIDATE_PARANOID )
        FT_INVALID_TOO_SHORT;

      /* ftxvalidator and FontValidator both warn and continue */
      maxEntry = (FT_Byte)( *length_p / entrySize - 1 );
      GXV_TRACE(( "too large maxEntry, shrinking to %d fit EntryTable length\n",
                  maxEntry ));
    }

    for ( entry = 0; entry <= maxEntry; entry++ )
    {
      FT_UShort  newState;
      FT_UShort  flags;


      GXV_LIMIT_CHECK( 2 + 2 );
      newState = FT_NEXT_USHORT( p );
      flags    = FT_NEXT_USHORT( p );


      if ( newState < stateArray                     ||
           stateArray + stateArray_length < newState )
      {
        GXV_TRACE(( " newState offset 0x%04x is out of stateArray\n",
                    newState ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_OFFSET;
        continue;
      }

      if ( 0 != ( ( newState - stateArray ) % ( 1 + maxClassID ) ) )
      {
        GXV_TRACE(( " newState offset 0x%04x is not aligned to %d-classes\n",
                    newState,  1 + maxClassID ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_OFFSET;
        continue;
      }

      state = (FT_Byte)( ( newState - stateArray ) / ( 1 + maxClassID ) );

      switch ( GXV_GLYPHOFFSET_FMT( statetable ) )
      {
      case GXV_GLYPHOFFSET_NONE:
        glyphOffset.uc = 0;  /* make compiler happy */
        break;

      case GXV_GLYPHOFFSET_UCHAR:
        glyphOffset.uc = FT_NEXT_BYTE( p );
        break;

      case GXV_GLYPHOFFSET_CHAR:
        glyphOffset.c = FT_NEXT_CHAR( p );
        break;

      case GXV_GLYPHOFFSET_USHORT:
        glyphOffset.u = FT_NEXT_USHORT( p );
        break;

      case GXV_GLYPHOFFSET_SHORT:
        glyphOffset.s = FT_NEXT_SHORT( p );
        break;

      case GXV_GLYPHOFFSET_ULONG:
        glyphOffset.ul = FT_NEXT_ULONG( p );
        break;

      case GXV_GLYPHOFFSET_LONG:
        glyphOffset.l = FT_NEXT_LONG( p );
        break;

      default:
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_FORMAT;
        goto Exit;
      }

      if ( NULL != valid->statetable.entry_validate_func )
        valid->statetable.entry_validate_func( state,
                                               flags,
                                               &glyphOffset,
                                               statetable_table,
                                               statetable_limit,
                                               valid );
    }

  Exit:
    *length_p = (FT_UShort)( p - table );

    GXV_EXIT;
  }


  /* =========================== State Table ============================= */

  FT_LOCAL_DEF( void )
  gxv_StateTable_subtable_setup( FT_UShort      table_size,
                                 FT_UShort      classTable,
                                 FT_UShort      stateArray,
                                 FT_UShort      entryTable,
                                 FT_UShort*     classTable_length_p,
                                 FT_UShort*     stateArray_length_p,
                                 FT_UShort*     entryTable_length_p,
                                 GXV_Validator  valid )
  {
    FT_UShort   o[3];
    FT_UShort*  l[3];
    FT_UShort   buff[4];


    o[0] = classTable;
    o[1] = stateArray;
    o[2] = entryTable;
    l[0] = classTable_length_p;
    l[1] = stateArray_length_p;
    l[2] = entryTable_length_p;

    gxv_set_length_by_ushort_offset( o, l, buff, 3, table_size, valid );
  }


  FT_LOCAL_DEF( void )
  gxv_StateTable_validate( FT_Bytes       table,
                           FT_Bytes       limit,
                           GXV_Validator  valid )
  {
    FT_UShort   stateSize;
    FT_UShort   classTable;     /* offset to Class(Sub)Table */
    FT_UShort   stateArray;     /* offset to StateArray */
    FT_UShort   entryTable;     /* offset to EntryTable */

    FT_UShort   classTable_length;
    FT_UShort   stateArray_length;
    FT_UShort   entryTable_length;
    FT_Byte     maxClassID;
    FT_Byte     maxState;
    FT_Byte     maxEntry;

    GXV_StateTable_Subtable_Setup_Func  setup_func;

    FT_Bytes    p = table;


    GXV_NAME_ENTER( "StateTable" );

    GXV_TRACE(( "StateTable header\n" ));

    GXV_LIMIT_CHECK( 2 + 2 + 2 + 2 );
    stateSize  = FT_NEXT_USHORT( p );
    classTable = FT_NEXT_USHORT( p );
    stateArray = FT_NEXT_USHORT( p );
    entryTable = FT_NEXT_USHORT( p );

    GXV_TRACE(( "stateSize=0x%04x\n", stateSize ));
    GXV_TRACE(( "offset to classTable=0x%04x\n", classTable ));
    GXV_TRACE(( "offset to stateArray=0x%04x\n", stateArray ));
    GXV_TRACE(( "offset to entryTable=0x%04x\n", entryTable ));

    if ( stateSize > 0xFF )
      FT_INVALID_DATA;

    if ( valid->statetable.optdata_load_func != NULL )
      valid->statetable.optdata_load_func( p, limit, valid );

    if ( valid->statetable.subtable_setup_func != NULL)
      setup_func = valid->statetable.subtable_setup_func;
    else
      setup_func = gxv_StateTable_subtable_setup;

    setup_func( (FT_UShort)( limit - table ),
                classTable,
                stateArray,
                entryTable,
                &classTable_length,
                &stateArray_length,
                &entryTable_length,
                valid );

    GXV_TRACE(( "StateTable Subtables\n" ));

    if ( classTable != 0 )
      gxv_ClassTable_validate( table + classTable,
                               &classTable_length,
                               stateSize,
                               &maxClassID,
                               valid );
    else
      maxClassID = (FT_Byte)( stateSize - 1 );

    if ( stateArray != 0 )
      gxv_StateArray_validate( table + stateArray,
                               &stateArray_length,
                               maxClassID,
                               stateSize,
                               &maxState,
                               &maxEntry,
                               valid );
    else
    {
      maxState = 1;     /* 0:start of text, 1:start of line are predefined */
      maxEntry = 0;
    }

    if ( maxEntry > 0 && entryTable == 0 )
      FT_INVALID_OFFSET;

    if ( entryTable != 0 )
      gxv_EntryTable_validate( table + entryTable,
                               &entryTable_length,
                               maxEntry,
                               stateArray,
                               stateArray_length,
                               maxClassID,
                               table,
                               limit,
                               valid );

    GXV_EXIT;
  }


  /* ================= eXtended State Table (for morx) =================== */

  FT_LOCAL_DEF( void )
  gxv_XStateTable_subtable_setup( FT_ULong       table_size,
                                  FT_ULong       classTable,
                                  FT_ULong       stateArray,
                                  FT_ULong       entryTable,
                                  FT_ULong*      classTable_length_p,
                                  FT_ULong*      stateArray_length_p,
                                  FT_ULong*      entryTable_length_p,
                                  GXV_Validator  valid )
  {
    FT_ULong   o[3];
    FT_ULong*  l[3];
    FT_ULong   buff[4];


    o[0] = classTable;
    o[1] = stateArray;
    o[2] = entryTable;
    l[0] = classTable_length_p;
    l[1] = stateArray_length_p;
    l[2] = entryTable_length_p;

    gxv_set_length_by_ulong_offset( o, l, buff, 4, table_size, valid );
  }


  static void
  gxv_XClassTable_lookupval_validate( FT_UShort            glyph,
                                      GXV_LookupValueCPtr  value_p,
                                      GXV_Validator        valid )
  {
    FT_UNUSED( glyph );

    if ( value_p->u >= valid->xstatetable.nClasses )
      FT_INVALID_DATA;
    if ( value_p->u > valid->xstatetable.maxClassID )
      valid->xstatetable.maxClassID = value_p->u;
  }


  /*
    +===============+ --------+
    | lookup header |         |
    +===============+         |
    | BinSrchHeader |         |
    +===============+         |
    | lastGlyph[0]  |         |
    +---------------+         |
    | firstGlyph[0] |         |    head of lookup table
    +---------------+         |             +
    | offset[0]     |    ->   |          offset            [byte]
    +===============+         |             +
    | lastGlyph[1]  |         | (glyphID - firstGlyph) * 2 [byte]
    +---------------+         |
    | firstGlyph[1] |         |
    +---------------+         |
    | offset[1]     |         |
    +===============+         |
                              |
     ....                     |
                              |
    16bit value array         |
    +===============+         |
    |     value     | <-------+
     ....
  */
  static GXV_LookupValueDesc
  gxv_XClassTable_lookupfmt4_transit( FT_UShort            relative_gindex,
                                      GXV_LookupValueCPtr  base_value_p,
                                      FT_Bytes             lookuptbl_limit,
                                      GXV_Validator        valid )
  {
    FT_Bytes             p;
    FT_Bytes             limit;
    FT_UShort            offset;
    GXV_LookupValueDesc  value;

    /* XXX: check range? */
    offset = (FT_UShort)( base_value_p->u +
                          relative_gindex * sizeof ( FT_UShort ) );

    p     = valid->lookuptbl_head + offset;
    limit = lookuptbl_limit;

    GXV_LIMIT_CHECK ( 2 );
    value.u = FT_NEXT_USHORT( p );

    return value;
  }


  static void
  gxv_XStateArray_validate( FT_Bytes       table,
                            FT_ULong*      length_p,
                            FT_UShort      maxClassID,
                            FT_ULong       stateSize,
                            FT_UShort*     maxState_p,
                            FT_UShort*     maxEntry_p,
                            GXV_Validator  valid )
  {
    FT_Bytes   p = table;
    FT_Bytes   limit = table + *length_p;
    FT_UShort  clazz;
    FT_UShort  entry;

    FT_UNUSED( stateSize ); /* for the non-debugging case */


    GXV_NAME_ENTER( "XStateArray" );

    GXV_TRACE(( "parse % 3d bytes by stateSize=% 3d maxClassID=% 3d\n",
                (int)(*length_p), stateSize, (int)(maxClassID) ));

    /*
     * 2 states are predefined and must be described:
     * state 0 (start of text), 1 (start of line)
     */
    GXV_LIMIT_CHECK( ( 1 + maxClassID ) * 2 * 2 );

    *maxState_p = 0;
    *maxEntry_p = 0;

    /* read if enough to read another state */
    while ( p + ( ( 1 + maxClassID ) * 2 ) <= limit )
    {
      (*maxState_p)++;
      for ( clazz = 0; clazz <= maxClassID; clazz++ )
      {
        entry = FT_NEXT_USHORT( p );
        *maxEntry_p = (FT_UShort)FT_MAX( *maxEntry_p, entry );
      }
    }
    GXV_TRACE(( "parsed: maxState=%d, maxEntry=%d\n",
                *maxState_p, *maxEntry_p ));

    *length_p = p - table;

    GXV_EXIT;
  }


  static void
  gxv_XEntryTable_validate( FT_Bytes       table,
                            FT_ULong*      length_p,
                            FT_UShort      maxEntry,
                            FT_ULong       stateArray_length,
                            FT_UShort      maxClassID,
                            FT_Bytes       xstatetable_table,
                            FT_Bytes       xstatetable_limit,
                            GXV_Validator  valid )
  {
    FT_Bytes   p = table;
    FT_Bytes   limit = table + *length_p;
    FT_UShort  entry;
    FT_UShort  state;
    FT_Int     entrySize = 2 + 2 + GXV_GLYPHOFFSET_SIZE( xstatetable );


    GXV_NAME_ENTER( "XEntryTable" );
    GXV_TRACE(( "maxEntry=%d entrySize=%d\n", maxEntry, entrySize ));

    if ( ( p + ( maxEntry + 1 ) * entrySize ) > limit )
      FT_INVALID_TOO_SHORT;

    for (entry = 0; entry <= maxEntry ; entry++ )
    {
      FT_UShort                        newState_idx;
      FT_UShort                        flags;
      GXV_XStateTable_GlyphOffsetDesc  glyphOffset;


      GXV_LIMIT_CHECK( 2 + 2 );
      newState_idx = FT_NEXT_USHORT( p );
      flags        = FT_NEXT_USHORT( p );

      if ( stateArray_length < (FT_ULong)( newState_idx * 2 ) )
      {
        GXV_TRACE(( "  newState index 0x%04x points out of stateArray\n",
                    newState_idx ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_OFFSET;
      }

      state = (FT_UShort)( newState_idx / ( 1 + maxClassID ) );
      if ( 0 != ( newState_idx % ( 1 + maxClassID ) ) )
      {
        FT_TRACE4(( "-> new state = %d (supposed)\n"
                    "but newState index 0x%04x is not aligned to %d-classes\n",
                    state, newState_idx,  1 + maxClassID ));
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_OFFSET;
      }

      switch ( GXV_GLYPHOFFSET_FMT( xstatetable ) )
      {
      case GXV_GLYPHOFFSET_NONE:
        glyphOffset.uc = 0; /* make compiler happy */
        break;

      case GXV_GLYPHOFFSET_UCHAR:
        glyphOffset.uc = FT_NEXT_BYTE( p );
        break;

      case GXV_GLYPHOFFSET_CHAR:
        glyphOffset.c = FT_NEXT_CHAR( p );
        break;

      case GXV_GLYPHOFFSET_USHORT:
        glyphOffset.u = FT_NEXT_USHORT( p );
        break;

      case GXV_GLYPHOFFSET_SHORT:
        glyphOffset.s = FT_NEXT_SHORT( p );
        break;

      case GXV_GLYPHOFFSET_ULONG:
        glyphOffset.ul = FT_NEXT_ULONG( p );
        break;

      case GXV_GLYPHOFFSET_LONG:
        glyphOffset.l = FT_NEXT_LONG( p );
        break;

      default:
        if ( valid->root->level >= FT_VALIDATE_PARANOID )
          FT_INVALID_FORMAT;
        goto Exit;
      }

      if ( NULL != valid->xstatetable.entry_validate_func )
        valid->xstatetable.entry_validate_func( state,
                                                flags,
                                                &glyphOffset,
                                                xstatetable_table,
                                                xstatetable_limit,
                                                valid );
    }

  Exit:
    *length_p = p - table;

    GXV_EXIT;
  }


  FT_LOCAL_DEF( void )
  gxv_XStateTable_validate( FT_Bytes       table,
                            FT_Bytes       limit,
                            GXV_Validator  valid )
  {
    /* StateHeader members */
    FT_ULong   classTable;      /* offset to Class(Sub)Table */
    FT_ULong   stateArray;      /* offset to StateArray */
    FT_ULong   entryTable;      /* offset to EntryTable */

    FT_ULong   classTable_length;
    FT_ULong   stateArray_length;
    FT_ULong   entryTable_length;
    FT_UShort  maxState;
    FT_UShort  maxEntry;

    GXV_XStateTable_Subtable_Setup_Func  setup_func;

    FT_Bytes   p = table;


    GXV_NAME_ENTER( "XStateTable" );

    GXV_TRACE(( "XStateTable header\n" ));

    GXV_LIMIT_CHECK( 4 + 4 + 4 + 4 );
    valid->xstatetable.nClasses = FT_NEXT_ULONG( p );
    classTable = FT_NEXT_ULONG( p );
    stateArray = FT_NEXT_ULONG( p );
    entryTable = FT_NEXT_ULONG( p );

    GXV_TRACE(( "nClasses =0x%08x\n", valid->xstatetable.nClasses ));
    GXV_TRACE(( "offset to classTable=0x%08x\n", classTable ));
    GXV_TRACE(( "offset to stateArray=0x%08x\n", stateArray ));
    GXV_TRACE(( "offset to entryTable=0x%08x\n", entryTable ));

    if ( valid->xstatetable.nClasses > 0xFFFFU )
      FT_INVALID_DATA;

    GXV_TRACE(( "StateTable Subtables\n" ));

    if ( valid->xstatetable.optdata_load_func != NULL )
      valid->xstatetable.optdata_load_func( p, limit, valid );

    if ( valid->xstatetable.subtable_setup_func != NULL )
      setup_func = valid->xstatetable.subtable_setup_func;
    else
      setup_func = gxv_XStateTable_subtable_setup;

    setup_func( limit - table,
                classTable,
                stateArray,
                entryTable,
                &classTable_length,
                &stateArray_length,
                &entryTable_length,
                valid );

    if ( classTable != 0 )
    {
      valid->xstatetable.maxClassID = 0;
      valid->lookupval_sign         = GXV_LOOKUPVALUE_UNSIGNED;
      valid->lookupval_func         = gxv_XClassTable_lookupval_validate;
      valid->lookupfmt4_trans       = gxv_XClassTable_lookupfmt4_transit;
      gxv_LookupTable_validate( table + classTable,
                                table + classTable + classTable_length,
                                valid );
      if ( valid->subtable_length < classTable_length )
        classTable_length = valid->subtable_length;
    }
    else
    {
      /* XXX: check range? */
      valid->xstatetable.maxClassID =
        (FT_UShort)( valid->xstatetable.nClasses - 1 );
    }

    if ( stateArray != 0 )
      gxv_XStateArray_validate( table + stateArray,
                                &stateArray_length,
                                valid->xstatetable.maxClassID,
                                valid->xstatetable.nClasses,
                                &maxState,
                                &maxEntry,
                                valid );
    else
    {
      maxState = 1; /* 0:start of text, 1:start of line are predefined */
      maxEntry = 0;
    }

    if ( maxEntry > 0 && entryTable == 0 )
      FT_INVALID_OFFSET;

    if ( entryTable != 0 )
      gxv_XEntryTable_validate( table + entryTable,
                                &entryTable_length,
                                maxEntry,
                                stateArray_length,
                                valid->xstatetable.maxClassID,
                                table,
                                limit,
                                valid );

    GXV_EXIT;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                        Table overlapping                      *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  static int
  gxv_compare_ranges( FT_Bytes  table1_start,
                      FT_ULong  table1_length,
                      FT_Bytes  table2_start,
                      FT_ULong  table2_length )
  {
    if ( table1_start == table2_start )
    {
      if ( ( table1_length == 0 || table2_length == 0 ) )
        goto Out;
    }
    else if ( table1_start < table2_start )
    {
      if ( ( table1_start + table1_length ) <= table2_start )
        goto Out;
    }
    else if ( table1_start > table2_start )
    {
      if ( ( table1_start >= table2_start + table2_length ) )
        goto Out;
    }
    return 1;

  Out:
    return 0;
  }


  FT_LOCAL_DEF( void )
  gxv_odtect_add_range( FT_Bytes          start,
                        FT_ULong          length,
                        const FT_String*  name,
                        GXV_odtect_Range  odtect )
  {
    odtect->range[ odtect->nRanges ].start  = start;
    odtect->range[ odtect->nRanges ].length = length;
    odtect->range[ odtect->nRanges ].name   = (FT_String*)name;
    odtect->nRanges++;
  }


  FT_LOCAL_DEF( void )
  gxv_odtect_validate( GXV_odtect_Range  odtect,
                       GXV_Validator     valid )
  {
    FT_UInt  i, j;


    GXV_NAME_ENTER( "check overlap among multi ranges" );

    for ( i = 0; i < odtect->nRanges; i++ )
      for ( j = 0; j < i; j++ )
        if ( 0 != gxv_compare_ranges( odtect->range[i].start,
                                      odtect->range[i].length,
                                      odtect->range[j].start,
                                      odtect->range[j].length ) )
        {
          if ( odtect->range[i].name || odtect->range[j].name )
            GXV_TRACE(( "found overlap between range %d and range %d\n",
                        i, j ));
          else
            GXV_TRACE(( "found overlap between `%s' and `%s\'\n",
                        odtect->range[i].name,
                        odtect->range[j].name ));
          FT_INVALID_OFFSET;
        }

    GXV_EXIT;
  }


/* END */