Edit

kc3-lang/freetype/src/sfnt/sfobjs.c

Branch :

  • Show log

    Commit

  • Author : Werner Lemberg
    Date : 2025-07-04 19:52:53
    Hash : b04db387
    Message : [sfnt] Rewrite GPOS kerning support. (2/2) The previous code had a fundamental flaw: it didn't validate the necessary parts of the 'GPOS' table before accessing it, causing crashes with malformed data (since `TT_CONFIG_OPTION_GPOS_KERNING` is off by default, standard fuzzers don't catch these problems). Additionally, it did a lot of parsing while accessing kerning data, making it rather slow. The new implementation fixes this. After validation, offsets to the 'GPOS' lookup subtables used in the 'kern' feature that correspond to 'simple' kerning (i.e., similar to 'kern' table kerning) are stored in `TT_Face`; this greatly simplifies and accelerates access to the kerning data. Testing with font `SF-Pro.ttf` version '1.00', the validation time for the 'GPOS' table increases the start-up time of `FT_New_Face` by less than 1%, while calls to `FT_Get_Kerning` become about 3.5 times faster. * include/freetype/internal (gpos_kerning_available): Replace with... (gpos_lookups_kerning, num_gpos_lookups_kerning): ... these new fields. Update callers. * src/ttgpos.c [TT_CONFIG_OPTION_GPOS_KERNING]: A new implementation.

  • src/sfnt/sfobjs.c
  • /****************************************************************************
     *
     * sfobjs.c
     *
     *   SFNT object management (base).
     *
     * Copyright (C) 1996-2024 by
     * David Turner, Robert Wilhelm, and Werner Lemberg.
     *
     * This file is part of the FreeType project, and may only be used,
     * modified, and distributed under the terms of the FreeType project
     * license, LICENSE.TXT.  By continuing to use, modify, or distribute
     * this file you indicate that you have read the license and
     * understand and accept it fully.
     *
     */
    
    
    #include "sfobjs.h"
    #include "ttload.h"
    #include "ttcmap.h"
    #include "ttkern.h"
    #include "sfwoff.h"
    #include "sfwoff2.h"
    #include <freetype/internal/sfnt.h>
    #include <freetype/internal/ftdebug.h>
    #include <freetype/ttnameid.h>
    #include <freetype/tttags.h>
    #include <freetype/internal/services/svpscmap.h>
    #include <freetype/ftsnames.h>
    
    #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
    #include <freetype/internal/services/svmm.h>
    #include <freetype/internal/services/svmetric.h>
    #endif
    
    #include "sferrors.h"
    
    #ifdef TT_CONFIG_OPTION_BDF
    #include "ttbdf.h"
    #endif
    
    #ifdef TT_CONFIG_OPTION_GPOS_KERNING
    #include "ttgpos.h"
    #endif
    
    
      /**************************************************************************
       *
       * 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  sfobjs
    
    
    
      /* convert a UTF-16 name entry to ASCII */
      static FT_String*
      tt_name_ascii_from_utf16( TT_Name    entry,
                                FT_Memory  memory )
      {
        FT_String*  string = NULL;
        FT_UInt     len, code, n;
        FT_Byte*    read   = (FT_Byte*)entry->string;
        FT_Error    error;
    
    
        len = (FT_UInt)entry->stringLength / 2;
    
        if ( FT_QNEW_ARRAY( string, len + 1 ) )
          return NULL;
    
        for ( n = 0; n < len; n++ )
        {
          code = FT_NEXT_USHORT( read );
    
          if ( code == 0 )
            break;
    
          if ( code < 32 || code > 127 )
            code = '?';
    
          string[n] = (char)code;
        }
    
        string[n] = 0;
    
        return string;
      }
    
    
      /* convert an Apple Roman or symbol name entry to ASCII */
      static FT_String*
      tt_name_ascii_from_other( TT_Name    entry,
                                FT_Memory  memory )
      {
        FT_String*  string = NULL;
        FT_UInt     len, code, n;
        FT_Byte*    read   = (FT_Byte*)entry->string;
        FT_Error    error;
    
    
        len = (FT_UInt)entry->stringLength;
    
        if ( FT_QNEW_ARRAY( string, len + 1 ) )
          return NULL;
    
        for ( n = 0; n < len; n++ )
        {
          code = *read++;
    
          if ( code == 0 )
            break;
    
          if ( code < 32 || code > 127 )
            code = '?';
    
          string[n] = (char)code;
        }
    
        string[n] = 0;
    
        return string;
      }
    
    
      typedef FT_String*  (*TT_Name_ConvertFunc)( TT_Name    entry,
                                                  FT_Memory  memory );
    
    
      /* documentation is in sfnt.h */
    
      FT_LOCAL_DEF( FT_Error )
      tt_face_get_name( TT_Face      face,
                        FT_UShort    nameid,
                        FT_String**  name )
      {
        FT_Memory   memory = face->root.memory;
        FT_Error    error  = FT_Err_Ok;
        FT_String*  result = NULL;
        FT_UShort   n;
        TT_Name     rec;
    
        FT_Int  found_apple         = -1;
        FT_Int  found_apple_roman   = -1;
        FT_Int  found_apple_english = -1;
        FT_Int  found_win           = -1;
        FT_Int  found_unicode       = -1;
    
        FT_Bool  is_english = 0;
    
        TT_Name_ConvertFunc  convert;
    
    
        FT_ASSERT( name );
    
        rec = face->name_table.names;
        for ( n = 0; n < face->num_names; n++, rec++ )
        {
          /* According to the OpenType 1.3 specification, only Microsoft or  */
          /* Apple platform IDs might be used in the `name' table.  The      */
          /* `Unicode' platform is reserved for the `cmap' table, and the    */
          /* `ISO' one is deprecated.                                        */
          /*                                                                 */
          /* However, the Apple TrueType specification doesn't say the same  */
          /* thing and goes to suggest that all Unicode `name' table entries */
          /* should be coded in UTF-16 (in big-endian format I suppose).     */
          /*                                                                 */
          if ( rec->nameID == nameid && rec->stringLength > 0 )
          {
            switch ( rec->platformID )
            {
            case TT_PLATFORM_APPLE_UNICODE:
            case TT_PLATFORM_ISO:
              /* there is `languageID' to check there.  We should use this */
              /* field only as a last solution when nothing else is        */
              /* available.                                                */
              /*                                                           */
              found_unicode = n;
              break;
    
            case TT_PLATFORM_MACINTOSH:
              /* This is a bit special because some fonts will use either    */
              /* an English language id, or a Roman encoding id, to indicate */
              /* the English version of its font name.                       */
              /*                                                             */
              if ( rec->languageID == TT_MAC_LANGID_ENGLISH )
                found_apple_english = n;
              else if ( rec->encodingID == TT_MAC_ID_ROMAN )
                found_apple_roman = n;
              break;
    
            case TT_PLATFORM_MICROSOFT:
              /* we only take a non-English name when there is nothing */
              /* else available in the font                            */
              /*                                                       */
              if ( found_win == -1 || ( rec->languageID & 0x3FF ) == 0x009 )
              {
                switch ( rec->encodingID )
                {
                case TT_MS_ID_SYMBOL_CS:
                case TT_MS_ID_UNICODE_CS:
                case TT_MS_ID_UCS_4:
                  is_english = FT_BOOL( ( rec->languageID & 0x3FF ) == 0x009 );
                  found_win  = n;
                  break;
    
                default:
                  ;
                }
              }
              break;
    
            default:
              ;
            }
          }
        }
    
        found_apple = found_apple_roman;
        if ( found_apple_english >= 0 )
          found_apple = found_apple_english;
    
        /* some fonts contain invalid Unicode or Macintosh formatted entries; */
        /* we will thus favor names encoded in Windows formats if available   */
        /* (provided it is an English name)                                   */
        /*                                                                    */
        convert = NULL;
        if ( found_win >= 0 && !( found_apple >= 0 && !is_english ) )
        {
          rec = face->name_table.names + found_win;
          switch ( rec->encodingID )
          {
            /* all Unicode strings are encoded using UTF-16BE */
          case TT_MS_ID_UNICODE_CS:
          case TT_MS_ID_SYMBOL_CS:
            convert = tt_name_ascii_from_utf16;
            break;
    
          case TT_MS_ID_UCS_4:
            /* Apparently, if this value is found in a name table entry, it is */
            /* documented as `full Unicode repertoire'.  Experience with the   */
            /* MsGothic font shipped with Windows Vista shows that this really */
            /* means UTF-16 encoded names (UCS-4 values are only used within   */
            /* charmaps).                                                      */
            convert = tt_name_ascii_from_utf16;
            break;
    
          default:
            ;
          }
        }
        else if ( found_apple >= 0 )
        {
          rec     = face->name_table.names + found_apple;
          convert = tt_name_ascii_from_other;
        }
        else if ( found_unicode >= 0 )
        {
          rec     = face->name_table.names + found_unicode;
          convert = tt_name_ascii_from_utf16;
        }
    
        if ( rec && convert )
        {
          if ( !rec->string )
          {
            FT_Stream  stream = face->name_table.stream;
    
    
            if ( FT_QNEW_ARRAY ( rec->string, rec->stringLength ) ||
                 FT_STREAM_SEEK( rec->stringOffset )              ||
                 FT_STREAM_READ( rec->string, rec->stringLength ) )
            {
              FT_FREE( rec->string );
              rec->stringLength = 0;
              result            = NULL;
              goto Exit;
            }
          }
    
          result = convert( rec, memory );
        }
    
      Exit:
        *name = result;
        return error;
      }
    
    
      static FT_Encoding
      sfnt_find_encoding( int  platform_id,
                          int  encoding_id )
      {
        typedef struct  TEncoding_
        {
          int          platform_id;
          int          encoding_id;
          FT_Encoding  encoding;
    
        } TEncoding;
    
        static
        const TEncoding  tt_encodings[] =
        {
          { TT_PLATFORM_ISO,           -1,                  FT_ENCODING_UNICODE },
    
          { TT_PLATFORM_APPLE_UNICODE, -1,                  FT_ENCODING_UNICODE },
    
          { TT_PLATFORM_MACINTOSH,     TT_MAC_ID_ROMAN,     FT_ENCODING_APPLE_ROMAN },
    
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_SYMBOL_CS,  FT_ENCODING_MS_SYMBOL },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_UCS_4,      FT_ENCODING_UNICODE },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_UNICODE_CS, FT_ENCODING_UNICODE },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_SJIS,       FT_ENCODING_SJIS },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_PRC,        FT_ENCODING_PRC },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_BIG_5,      FT_ENCODING_BIG5 },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_WANSUNG,    FT_ENCODING_WANSUNG },
          { TT_PLATFORM_MICROSOFT,     TT_MS_ID_JOHAB,      FT_ENCODING_JOHAB }
        };
    
        const TEncoding  *cur, *limit;
    
    
        cur   = tt_encodings;
        limit = cur + sizeof ( tt_encodings ) / sizeof ( tt_encodings[0] );
    
        for ( ; cur < limit; cur++ )
        {
          if ( cur->platform_id == platform_id )
          {
            if ( cur->encoding_id == encoding_id ||
                 cur->encoding_id == -1          )
              return cur->encoding;
          }
        }
    
        return FT_ENCODING_NONE;
      }
    
    
      /* Fill in face->ttc_header.  If the font is not a TTC, it is */
      /* synthesized into a TTC with one offset table.              */
      static FT_Error
      sfnt_open_font( FT_Stream  stream,
                      TT_Face    face,
                      FT_Int*    face_instance_index,
                      FT_Long*   woff2_num_faces )
      {
        FT_Memory  memory = stream->memory;
        FT_Error   error;
        FT_ULong   tag, offset;
    
        static const FT_Frame_Field  ttc_header_fields[] =
        {
    #undef  FT_STRUCTURE
    #define FT_STRUCTURE  TTC_HeaderRec
    
          FT_FRAME_START( 8 ),
            FT_FRAME_LONG( version ),
            FT_FRAME_LONG( count   ),  /* this is ULong in the specs */
          FT_FRAME_END
        };
    
    #ifndef FT_CONFIG_OPTION_USE_BROTLI
        FT_UNUSED( face_instance_index );
        FT_UNUSED( woff2_num_faces );
    #endif
    
    
        face->ttc_header.tag     = 0;
        face->ttc_header.version = 0;
        face->ttc_header.count   = 0;
    
    #if defined( FT_CONFIG_OPTION_USE_ZLIB )   || \
        defined( FT_CONFIG_OPTION_USE_BROTLI )
      retry:
    #endif
    
        offset = FT_STREAM_POS();
    
        if ( FT_READ_ULONG( tag ) )
          return error;
    
    #ifdef FT_CONFIG_OPTION_USE_ZLIB
        if ( tag == TTAG_wOFF )
        {
          FT_TRACE2(( "sfnt_open_font: file is a WOFF; synthesizing SFNT\n" ));
    
          if ( FT_STREAM_SEEK( offset ) )
            return error;
    
          error = woff_open_font( stream, face );
          if ( error )
            return error;
    
          /* Swap out stream and retry! */
          stream = face->root.stream;
          goto retry;
        }
    #endif
    
    #ifdef FT_CONFIG_OPTION_USE_BROTLI
        if ( tag == TTAG_wOF2 )
        {
          FT_TRACE2(( "sfnt_open_font: file is a WOFF2; synthesizing SFNT\n" ));
    
          if ( FT_STREAM_SEEK( offset ) )
            return error;
    
          error = woff2_open_font( stream,
                                   face,
                                   face_instance_index,
                                   woff2_num_faces );
          if ( error )
            return error;
    
          /* Swap out stream and retry! */
          stream = face->root.stream;
          goto retry;
        }
    #endif
    
        if ( tag != 0x00010000UL &&
             tag != TTAG_ttcf    &&
             tag != TTAG_OTTO    &&
             tag != TTAG_true    &&
             tag != TTAG_typ1    &&
             tag != TTAG_0xA5kbd &&
             tag != TTAG_0xA5lst &&
             tag != 0x00020000UL )
        {
          FT_TRACE2(( "  not a font using the SFNT container format\n" ));
          return FT_THROW( Unknown_File_Format );
        }
    
        face->ttc_header.tag = TTAG_ttcf;
    
        if ( tag == TTAG_ttcf )
        {
          FT_Int  n;
    
    
          FT_TRACE3(( "sfnt_open_font: file is a collection\n" ));
    
          if ( FT_STREAM_READ_FIELDS( ttc_header_fields, &face->ttc_header ) )
            return error;
    
          FT_TRACE3(( "                with %ld subfonts\n",
                      face->ttc_header.count ));
    
          if ( face->ttc_header.count == 0 )
            return FT_THROW( Invalid_Table );
    
          /* a rough size estimate: let's conservatively assume that there   */
          /* is just a single table info in each subfont header (12 + 16*1 = */
          /* 28 bytes), thus we have (at least) `12 + 4*count' bytes for the */
          /* size of the TTC header plus `28*count' bytes for all subfont    */
          /* headers                                                         */
          if ( (FT_ULong)face->ttc_header.count > stream->size / ( 28 + 4 ) )
            return FT_THROW( Array_Too_Large );
    
          /* now read the offsets of each font in the file */
          if ( FT_QNEW_ARRAY( face->ttc_header.offsets, face->ttc_header.count ) )
            return error;
    
          if ( FT_FRAME_ENTER( face->ttc_header.count * 4L ) )
            return error;
    
          for ( n = 0; n < face->ttc_header.count; n++ )
            face->ttc_header.offsets[n] = FT_GET_ULONG();
    
          FT_FRAME_EXIT();
        }
        else
        {
          FT_TRACE3(( "sfnt_open_font: synthesize TTC\n" ));
    
          face->ttc_header.version = 1 << 16;
          face->ttc_header.count   = 1;
    
          if ( FT_QNEW( face->ttc_header.offsets ) )
            return error;
    
          face->ttc_header.offsets[0] = offset;
        }
    
        return error;
      }
    
    
      FT_LOCAL_DEF( FT_Error )
      sfnt_init_face( FT_Stream      stream,
                      TT_Face        face,
                      FT_Int         face_instance_index,
                      FT_Int         num_params,
                      FT_Parameter*  params )
      {
        FT_Error      error;
        FT_Library    library         = face->root.driver->root.library;
        SFNT_Service  sfnt;
        FT_Int        face_index;
        FT_Long       woff2_num_faces = 0;
    
    
        /* for now, parameters are unused */
        FT_UNUSED( num_params );
        FT_UNUSED( params );
    
    
        sfnt = (SFNT_Service)face->sfnt;
        if ( !sfnt )
        {
          sfnt = (SFNT_Service)FT_Get_Module_Interface( library, "sfnt" );
          if ( !sfnt )
          {
            FT_ERROR(( "sfnt_init_face: cannot access `sfnt' module\n" ));
            return FT_THROW( Missing_Module );
          }
    
          face->sfnt       = sfnt;
          face->goto_table = sfnt->goto_table;
        }
    
        FT_FACE_FIND_GLOBAL_SERVICE( face, face->psnames, POSTSCRIPT_CMAPS );
    
    #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
        if ( !face->mm )
        {
          /* we want the MM interface from the `truetype' module only */
          FT_Module  tt_module = FT_Get_Module( library, "truetype" );
    
    
          face->mm = ft_module_get_service( tt_module,
                                            FT_SERVICE_ID_MULTI_MASTERS,
                                            0 );
        }
    
        if ( !face->tt_var )
        {
          /* we want the metrics variations interface */
          /* from the `truetype' module only          */
          FT_Module  tt_module = FT_Get_Module( library, "truetype" );
    
    
          face->tt_var = ft_module_get_service( tt_module,
                                                FT_SERVICE_ID_METRICS_VARIATIONS,
                                                0 );
        }
    
        if ( !face->face_var )
          face->face_var = ft_module_get_service(
                             &face->root.driver->root,
                             FT_SERVICE_ID_METRICS_VARIATIONS,
                             0 );
    #endif
    
        FT_TRACE2(( "SFNT driver\n" ));
    
        error = sfnt_open_font( stream,
                                face,
                                &face_instance_index,
                                &woff2_num_faces );
        if ( error )
          return error;
    
        /* Stream may have changed in sfnt_open_font. */
        stream = face->root.stream;
    
        FT_TRACE2(( "sfnt_init_face: %p (index %d)\n",
                    (void *)face,
                    face_instance_index ));
    
        face_index = FT_ABS( face_instance_index ) & 0xFFFF;
    
        /* value -(N+1) requests information on index N */
        if ( face_instance_index < 0 && face_index > 0 )
          face_index--;
    
        if ( face_index >= face->ttc_header.count )
        {
          if ( face_instance_index >= 0 )
            return FT_THROW( Invalid_Argument );
          else
            face_index = 0;
        }
    
        if ( FT_STREAM_SEEK( face->ttc_header.offsets[face_index] ) )
          return error;
    
        /* check whether we have a valid TrueType file */
        error = sfnt->load_font_dir( face, stream );
        if ( error )
          return error;
    
    #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
        {
          FT_Memory  memory = face->root.memory;
    
          FT_ULong  fvar_len;
    
          FT_ULong  version;
          FT_ULong  offset;
    
          FT_UShort  num_axes;
          FT_UShort  axis_size;
          FT_UShort  num_instances;
          FT_UShort  instance_size;
    
          FT_Int  instance_index;
    
          FT_Byte*  default_values  = NULL;
          FT_Byte*  instance_values = NULL;
    
    
          instance_index = FT_ABS( face_instance_index ) >> 16;
    
          /* test whether current face is a GX font with named instances */
          if ( face->goto_table( face, TTAG_fvar, stream, &fvar_len ) ||
               fvar_len < 20                                          ||
               FT_READ_ULONG( version )                               ||
               FT_READ_USHORT( offset )                               ||
               FT_STREAM_SKIP( 2 ) /* reserved */                     ||
               FT_READ_USHORT( num_axes )                             ||
               FT_READ_USHORT( axis_size )                            ||
               FT_READ_USHORT( num_instances )                        ||
               FT_READ_USHORT( instance_size )                        )
          {
            version       = 0;
            offset        = 0;
            num_axes      = 0;
            axis_size     = 0;
            num_instances = 0;
            instance_size = 0;
          }
    
          /* check that the data is bound by the table length */
          if ( version != 0x00010000UL                    ||
               axis_size != 20                            ||
               num_axes == 0                              ||
               /* `num_axes' limit implied by 16-bit `instance_size' */
               num_axes > 0x3FFE                          ||
               !( instance_size == 4 + 4 * num_axes ||
                  instance_size == 6 + 4 * num_axes )     ||
               /* `num_instances' limit implied by limited range of name IDs */
               num_instances > 0x7EFF                     ||
               offset                          +
                 axis_size * num_axes          +
                 instance_size * num_instances > fvar_len )
            num_instances = 0;
          else
            face->variation_support |= TT_FACE_FLAG_VAR_FVAR;
    
          /*
           * As documented in the OpenType specification, an entry for the
           * default instance may be omitted in the named instance table.  In
           * particular this means that even if there is no named instance
           * table in the font we actually do have a named instance, namely the
           * default instance.
           *
           * For consistency, we always want the default instance in our list
           * of named instances.  If it is missing, we try to synthesize it
           * later on.  Here, we have to adjust `num_instances' accordingly.
           */
    
          if ( ( face->variation_support & TT_FACE_FLAG_VAR_FVAR ) &&
               !( FT_QALLOC(  default_values, num_axes * 4 ) ||
                  FT_QALLOC( instance_values, num_axes * 4 ) )     )
          {
            /* the current stream position is 16 bytes after the table start */
            FT_ULong  array_start = FT_STREAM_POS() - 16 + offset;
            FT_ULong  default_value_offset, instance_offset;
    
            FT_Byte*  p;
            FT_UInt   i;
    
    
            default_value_offset = array_start + 8;
            p                    = default_values;
    
            for ( i = 0; i < num_axes; i++ )
            {
              (void)FT_STREAM_READ_AT( default_value_offset, p, 4 );
    
              default_value_offset += axis_size;
              p                    += 4;
            }
    
            instance_offset = array_start + axis_size * num_axes + 4;
    
            for ( i = 0; i < num_instances; i++ )
            {
              (void)FT_STREAM_READ_AT( instance_offset,
                                       instance_values,
                                       num_axes * 4 );
    
              if ( !ft_memcmp( default_values, instance_values, num_axes * 4 ) )
                break;
    
              instance_offset += instance_size;
            }
    
            /* named instance indices start with value 1 */
            face->var_default_named_instance = i + 1;
    
            if ( i == num_instances )
            {
              /* no default instance in named instance table; */
              /* we thus have to synthesize it                */
              num_instances++;
            }
          }
    
          FT_FREE( default_values );
          FT_FREE( instance_values );
    
          /* we don't support Multiple Master CFFs yet; */
          /* note that `glyf' or `CFF2' have precedence */
          if ( face->goto_table( face, TTAG_glyf, stream, 0 ) &&
               face->goto_table( face, TTAG_CFF2, stream, 0 ) &&
               !face->goto_table( face, TTAG_CFF, stream, 0 ) )
            num_instances = 0;
    
          /* instance indices in `face_instance_index' start with index 1, */
          /* thus `>' and not `>='                                         */
          if ( instance_index > num_instances )
          {
            if ( face_instance_index >= 0 )
              return FT_THROW( Invalid_Argument );
            else
              num_instances = 0;
          }
    
          face->root.style_flags = (FT_Long)num_instances << 16;
        }
    #endif
    
        face->root.num_faces  = face->ttc_header.count;
        face->root.face_index = face_instance_index;
    
        /* `num_faces' for a WOFF2 needs to be handled separately. */
        if ( woff2_num_faces )
          face->root.num_faces = woff2_num_faces;
    
        return error;
      }
    
    
    #define LOAD_( x )                                          \
      do                                                        \
      {                                                         \
        FT_TRACE2(( "`" #x "' " ));                             \
        FT_TRACE3(( "-->\n" ));                                 \
                                                                \
        error = sfnt->load_ ## x( face, stream );               \
                                                                \
        FT_TRACE2(( "%s\n", ( !error )                          \
                            ? "loaded"                          \
                            : FT_ERR_EQ( error, Table_Missing ) \
                              ? "missing"                       \
                              : "failed to load" ));            \
        FT_TRACE3(( "\n" ));                                    \
      } while ( 0 )
    
    #define LOADM_( x, vertical )                               \
      do                                                        \
      {                                                         \
        FT_TRACE2(( "`%s" #x "' ",                              \
                    vertical ? "vertical " : "" ));             \
        FT_TRACE3(( "-->\n" ));                                 \
                                                                \
        error = sfnt->load_ ## x( face, stream, vertical );     \
                                                                \
        FT_TRACE2(( "%s\n", ( !error )                          \
                            ? "loaded"                          \
                            : FT_ERR_EQ( error, Table_Missing ) \
                              ? "missing"                       \
                              : "failed to load" ));            \
        FT_TRACE3(( "\n" ));                                    \
      } while ( 0 )
    
    #define GET_NAME( id, field )                                   \
      do                                                            \
      {                                                             \
        error = tt_face_get_name( face, TT_NAME_ID_ ## id, field ); \
        if ( error )                                                \
          goto Exit;                                                \
      } while ( 0 )
    
    
      FT_LOCAL_DEF( FT_Error )
      sfnt_load_face( FT_Stream      stream,
                      TT_Face        face,
                      FT_Int         face_instance_index,
                      FT_Int         num_params,
                      FT_Parameter*  params )
      {
        FT_Error  error;
    #ifdef TT_CONFIG_OPTION_POSTSCRIPT_NAMES
        FT_Error  psnames_error;
    #endif
    
        FT_Bool  has_outline;
        FT_Bool  is_apple_sbit;
    
        FT_Bool  has_CBLC;
        FT_Bool  has_CBDT;
        FT_Bool  has_EBLC;
        FT_Bool  has_bloc;
        FT_Bool  has_sbix;
    
        FT_Bool  ignore_typographic_family    = FALSE;
        FT_Bool  ignore_typographic_subfamily = FALSE;
        FT_Bool  ignore_sbix                  = FALSE;
    
        SFNT_Service  sfnt = (SFNT_Service)face->sfnt;
    
        FT_UNUSED( face_instance_index );
    
    
        /* Check parameters */
    
        {
          FT_Int  i;
    
    
          for ( i = 0; i < num_params; i++ )
          {
            if ( params[i].tag == FT_PARAM_TAG_IGNORE_TYPOGRAPHIC_FAMILY )
              ignore_typographic_family = TRUE;
            else if ( params[i].tag == FT_PARAM_TAG_IGNORE_TYPOGRAPHIC_SUBFAMILY )
              ignore_typographic_subfamily = TRUE;
            else if ( params[i].tag == FT_PARAM_TAG_IGNORE_SBIX )
              ignore_sbix = TRUE;
          }
        }
    
        /* Load tables */
    
        /* We now support two SFNT-based bitmapped font formats.  They */
        /* are recognized easily as they do not include a `glyf'       */
        /* table.                                                      */
        /*                                                             */
        /* The first format comes from Apple, and uses a table named   */
        /* `bhed' instead of `head' to store the font header (using    */
        /* the same format).  It also doesn't include horizontal and   */
        /* vertical metrics tables (i.e. `hhea' and `vhea' tables are  */
        /* missing).                                                   */
        /*                                                             */
        /* The other format comes from Microsoft, and is used with     */
        /* WinCE/PocketPC.  It looks like a standard TTF, except that  */
        /* it doesn't contain outlines.                                */
        /*                                                             */
    
        FT_TRACE2(( "sfnt_load_face: %p\n", (void *)face ));
        FT_TRACE2(( "\n" ));
    
        /* do we have outlines in there? */
    #ifdef FT_CONFIG_OPTION_INCREMENTAL
        has_outline = FT_BOOL( face->root.internal->incremental_interface ||
                               tt_face_lookup_table( face, TTAG_glyf )    ||
                               tt_face_lookup_table( face, TTAG_CFF )     ||
                               tt_face_lookup_table( face, TTAG_CFF2 )    );
    #else
        has_outline = FT_BOOL( tt_face_lookup_table( face, TTAG_glyf ) ||
                               tt_face_lookup_table( face, TTAG_CFF )  ||
                               tt_face_lookup_table( face, TTAG_CFF2 ) );
    #endif
    
        /* check which sbit formats are present */
        has_CBLC = !face->goto_table( face, TTAG_CBLC, stream, 0 );
        has_CBDT = !face->goto_table( face, TTAG_CBDT, stream, 0 );
        has_EBLC = !face->goto_table( face, TTAG_EBLC, stream, 0 );
        has_bloc = !face->goto_table( face, TTAG_bloc, stream, 0 );
        has_sbix = !face->goto_table( face, TTAG_sbix, stream, 0 );
    
        is_apple_sbit = FALSE;
    
        if ( ignore_sbix )
          has_sbix = FALSE;
    
        /* if this font doesn't contain outlines, we try to load */
        /* a `bhed' table                                        */
        if ( !has_outline && sfnt->load_bhed )
        {
          LOAD_( bhed );
          is_apple_sbit = FT_BOOL( !error );
        }
    
        /* load the font header (`head' table) if this isn't an Apple */
        /* sbit font file                                             */
        if ( !is_apple_sbit || has_sbix )
        {
          LOAD_( head );
          if ( error )
            goto Exit;
        }
    
        /* Ignore outlines for CBLC/CBDT fonts. */
        if ( has_CBLC || has_CBDT )
          has_outline = FALSE;
    
        /* OpenType 1.8.2 introduced limits to this value;    */
        /* however, they make sense for older SFNT fonts also */
        if ( face->header.Units_Per_EM <    16 ||
             face->header.Units_Per_EM > 16384 )
        {
          error = FT_THROW( Invalid_Table );
    
          goto Exit;
        }
    
        /* the following tables are often not present in embedded TrueType */
        /* fonts within PDF documents, so don't check for them.            */
        LOAD_( maxp );
        LOAD_( cmap );
    
        /* the following tables are optional in PCL fonts -- */
        /* don't check for errors                            */
        LOAD_( name );
        LOAD_( post );
    
    #ifdef TT_CONFIG_OPTION_POSTSCRIPT_NAMES
        psnames_error = error;
    #endif
    
        /* do not load the metrics headers and tables if this is an Apple */
        /* sbit font file                                                 */
        if ( !is_apple_sbit )
        {
          /* load the `hhea' and `hmtx' tables */
          LOADM_( hhea, 0 );
          if ( !error )
          {
            LOADM_( hmtx, 0 );
            if ( FT_ERR_EQ( error, Table_Missing ) )
            {
              error = FT_THROW( Hmtx_Table_Missing );
    
    #ifdef FT_CONFIG_OPTION_INCREMENTAL
              /* If this is an incrementally loaded font and there are */
              /* overriding metrics, tolerate a missing `hmtx' table.  */
              if ( face->root.internal->incremental_interface          &&
                   face->root.internal->incremental_interface->funcs->
                     get_glyph_metrics                                 )
              {
                face->horizontal.number_Of_HMetrics = 0;
                error                               = FT_Err_Ok;
              }
    #endif
            }
          }
          else if ( FT_ERR_EQ( error, Table_Missing ) )
          {
            /* No `hhea' table necessary for SFNT Mac fonts. */
            if ( face->format_tag == TTAG_true )
            {
              FT_TRACE2(( "This is an SFNT Mac font.\n" ));
    
              has_outline = 0;
              error       = FT_Err_Ok;
            }
            else
            {
              error = FT_THROW( Horiz_Header_Missing );
    
    #ifdef FT_CONFIG_OPTION_INCREMENTAL
              /* If this is an incrementally loaded font and there are */
              /* overriding metrics, tolerate a missing `hhea' table.  */
              if ( face->root.internal->incremental_interface          &&
                   face->root.internal->incremental_interface->funcs->
                     get_glyph_metrics                                 )
              {
                face->horizontal.number_Of_HMetrics = 0;
                error                               = FT_Err_Ok;
              }
    #endif
    
            }
          }
    
          if ( error )
            goto Exit;
    
          /* try to load the `vhea' and `vmtx' tables */
          LOADM_( hhea, 1 );
          if ( !error )
          {
            LOADM_( hmtx, 1 );
            if ( !error )
              face->vertical_info = 1;
          }
    
          if ( error && FT_ERR_NEQ( error, Table_Missing ) )
            goto Exit;
    
          LOAD_( os2 );
          if ( error )
          {
            /* we treat the table as missing if there are any errors */
            face->os2.version = 0xFFFFU;
          }
        }
    
        /* the optional tables */
    
        /* embedded bitmap support */
        /* TODO: Replace this clumsy check for all possible sbit tables     */
        /*       with something better (for example, by passing a parameter */
        /*       to suppress 'sbix' loading).                               */
        if ( sfnt->load_eblc                                  &&
             ( has_CBLC || has_EBLC || has_bloc || has_sbix ) )
          LOAD_( eblc );
    
        /* colored glyph support */
        if ( sfnt->load_cpal )
        {
          LOAD_( cpal );
          LOAD_( colr );
        }
    
        /* OpenType-SVG glyph support */
        if ( sfnt->load_svg )
          LOAD_( svg );
    
        /* consider the pclt, kerning, and gasp tables as optional */
        LOAD_( pclt );
        LOAD_( gasp );
        LOAD_( kern );
    
    #ifdef TT_CONFIG_OPTION_GPOS_KERNING
        LOAD_( gpos );
    #endif
    
        face->root.num_glyphs = face->max_profile.numGlyphs;
    
        /* Bit 8 of the `fsSelection' field in the `OS/2' table denotes  */
        /* a WWS-only font face.  `WWS' stands for `weight', width', and */
        /* `slope', a term used by Microsoft's Windows Presentation      */
        /* Foundation (WPF).  This flag has been introduced in version   */
        /* 1.5 of the OpenType specification (May 2008).                 */
    
        face->root.family_name = NULL;
        face->root.style_name  = NULL;
        if ( face->os2.version != 0xFFFFU && face->os2.fsSelection & 256 )
        {
          if ( !ignore_typographic_family )
            GET_NAME( TYPOGRAPHIC_FAMILY, &face->root.family_name );
          if ( !face->root.family_name )
            GET_NAME( FONT_FAMILY, &face->root.family_name );
    
          if ( !ignore_typographic_subfamily )
            GET_NAME( TYPOGRAPHIC_SUBFAMILY, &face->root.style_name );
          if ( !face->root.style_name )
            GET_NAME( FONT_SUBFAMILY, &face->root.style_name );
        }
        else
        {
          GET_NAME( WWS_FAMILY, &face->root.family_name );
          if ( !face->root.family_name && !ignore_typographic_family )
            GET_NAME( TYPOGRAPHIC_FAMILY, &face->root.family_name );
          if ( !face->root.family_name )
            GET_NAME( FONT_FAMILY, &face->root.family_name );
    
          GET_NAME( WWS_SUBFAMILY, &face->root.style_name );
          if ( !face->root.style_name && !ignore_typographic_subfamily )
            GET_NAME( TYPOGRAPHIC_SUBFAMILY, &face->root.style_name );
          if ( !face->root.style_name )
            GET_NAME( FONT_SUBFAMILY, &face->root.style_name );
        }
    
    #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
        {
          FT_Memory  memory = face->root.memory;
    
    
          if ( FT_STRDUP( face->non_var_style_name, face->root.style_name ) )
            goto Exit;
        }
    #endif
    
        /* now set up root fields */
        {
          FT_Face  root  = &face->root;
          FT_Long  flags = root->face_flags;
    
    
          /**********************************************************************
           *
           * Compute face flags.
           */
          if ( face->sbit_table_type == TT_SBIT_TABLE_TYPE_CBLC ||
               face->sbit_table_type == TT_SBIT_TABLE_TYPE_SBIX ||
               face->colr                                       ||
               face->svg                                        )
            flags |= FT_FACE_FLAG_COLOR;      /* color glyphs */
    
          if ( has_outline == TRUE )
          {
            /* by default (and for backward compatibility) we handle */
            /* fonts with an 'sbix' table as bitmap-only             */
            if ( has_sbix )
              flags |= FT_FACE_FLAG_SBIX;     /* with 'sbix' bitmaps */
            else
              flags |= FT_FACE_FLAG_SCALABLE; /* scalable outlines */
          }
    
          /* The sfnt driver only supports bitmap fonts natively, thus we */
          /* don't set FT_FACE_FLAG_HINTER.                               */
          flags |= FT_FACE_FLAG_SFNT       |  /* SFNT file format  */
                   FT_FACE_FLAG_HORIZONTAL;   /* horizontal data   */
    
    #ifdef TT_CONFIG_OPTION_POSTSCRIPT_NAMES
          if ( !psnames_error                             &&
               face->postscript.FormatType != 0x00030000L )
            flags |= FT_FACE_FLAG_GLYPH_NAMES;
    #endif
    
          /* fixed width font? */
          if ( face->postscript.isFixedPitch )
            flags |= FT_FACE_FLAG_FIXED_WIDTH;
    
          /* vertical information? */
          if ( face->vertical_info )
            flags |= FT_FACE_FLAG_VERTICAL;
    
          /* kerning available ? */
          if ( face->kern_avail_bits
    #ifdef TT_CONFIG_OPTION_GPOS_KERNING
               || face->num_gpos_lookups_kerning
    #endif
             )
            flags |= FT_FACE_FLAG_KERNING;
    
    #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
          /* Don't bother to load the tables unless somebody asks for them. */
          /* No need to do work which will (probably) not be used.          */
          if ( face->variation_support & TT_FACE_FLAG_VAR_FVAR )
            flags |= FT_FACE_FLAG_MULTIPLE_MASTERS;
    #endif
    
          root->face_flags = flags;
    
          /**********************************************************************
           *
           * Compute style flags.
           */
    
          flags = 0;
          if ( has_outline == TRUE && face->os2.version != 0xFFFFU )
          {
            /* We have an OS/2 table; use the `fsSelection' field.  Bit 9 */
            /* indicates an oblique font face.  This flag has been        */
            /* introduced in version 1.5 of the OpenType specification.   */
    
            if ( face->os2.fsSelection & 512 )       /* bit 9 */
              flags |= FT_STYLE_FLAG_ITALIC;
            else if ( face->os2.fsSelection & 1 )    /* bit 0 */
              flags |= FT_STYLE_FLAG_ITALIC;
    
            if ( face->os2.fsSelection & 32 )        /* bit 5 */
              flags |= FT_STYLE_FLAG_BOLD;
          }
          else
          {
            /* this is an old Mac font, use the header field */
    
            if ( face->header.Mac_Style & 1 )
              flags |= FT_STYLE_FLAG_BOLD;
    
            if ( face->header.Mac_Style & 2 )
              flags |= FT_STYLE_FLAG_ITALIC;
          }
    
          root->style_flags |= flags;
    
          /**********************************************************************
           *
           * Polish the charmaps.
           *
           *   Try to set the charmap encoding according to the platform &
           *   encoding ID of each charmap.  Emulate Unicode charmap if one
           *   is missing.
           */
    
          tt_face_build_cmaps( face );  /* ignore errors */
    
    
          /* set the encoding fields */
          {
            FT_Int   m;
    #ifdef FT_CONFIG_OPTION_POSTSCRIPT_NAMES
            FT_Bool  has_unicode = FALSE;
    #endif
    
    
            for ( m = 0; m < root->num_charmaps; m++ )
            {
              FT_CharMap  charmap = root->charmaps[m];
    
    
              charmap->encoding = sfnt_find_encoding( charmap->platform_id,
                                                      charmap->encoding_id );
    
    #ifdef FT_CONFIG_OPTION_POSTSCRIPT_NAMES
    
              if ( charmap->encoding == FT_ENCODING_UNICODE   ||
                   charmap->encoding == FT_ENCODING_MS_SYMBOL )  /* PUA */
                has_unicode = TRUE;
            }
    
            /* synthesize Unicode charmap if one is missing */
            if ( !has_unicode                                &&
                 root->face_flags & FT_FACE_FLAG_GLYPH_NAMES )
            {
              FT_CharMapRec  cmaprec;
    
    
              cmaprec.face        = root;
              cmaprec.platform_id = TT_PLATFORM_MICROSOFT;
              cmaprec.encoding_id = TT_MS_ID_UNICODE_CS;
              cmaprec.encoding    = FT_ENCODING_UNICODE;
    
    
              error = FT_CMap_New( (FT_CMap_Class)&tt_cmap_unicode_class_rec,
                                   NULL, &cmaprec, NULL );
              if ( error                                      &&
                   FT_ERR_NEQ( error, No_Unicode_Glyph_Name ) &&
                   FT_ERR_NEQ( error, Unimplemented_Feature ) )
                goto Exit;
              error = FT_Err_Ok;
    
    #endif /* FT_CONFIG_OPTION_POSTSCRIPT_NAMES */
    
            }
          }
    
    #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS
    
          /*
           * Now allocate the root array of FT_Bitmap_Size records and
           * populate them.  Unfortunately, it isn't possible to indicate bit
           * depths in the FT_Bitmap_Size record.  This is a design error.
           */
          {
            FT_UInt  count;
    
    
            count = face->sbit_num_strikes;
    
            if ( count > 0 )
            {
              FT_Memory        memory   = face->root.memory;
              FT_UShort        em_size  = face->header.Units_Per_EM;
              FT_Short         avgwidth = face->os2.xAvgCharWidth;
              FT_Size_Metrics  metrics;
    
              FT_UInt*  sbit_strike_map = NULL;
              FT_UInt   strike_idx, bsize_idx;
    
    
              if ( em_size == 0 || face->os2.version == 0xFFFFU )
              {
                avgwidth = 1;
                em_size = 1;
              }
    
              /* to avoid invalid strike data in the `available_sizes' field */
              /* of `FT_Face', we map `available_sizes' indices to strike    */
              /* indices                                                     */
              if ( FT_NEW_ARRAY( root->available_sizes, count ) ||
                   FT_QNEW_ARRAY( sbit_strike_map, count ) )
                goto Exit;
    
              bsize_idx = 0;
              for ( strike_idx = 0; strike_idx < count; strike_idx++ )
              {
                FT_Bitmap_Size*  bsize = root->available_sizes + bsize_idx;
    
    
                error = sfnt->load_strike_metrics( face, strike_idx, &metrics );
                if ( error )
                  continue;
    
                bsize->height = (FT_Short)( metrics.height >> 6 );
                bsize->width  = (FT_Short)(
                  ( avgwidth * metrics.x_ppem + em_size / 2 ) / em_size );
    
                bsize->x_ppem = metrics.x_ppem << 6;
                bsize->y_ppem = metrics.y_ppem << 6;
    
                /* assume 72dpi */
                bsize->size   = metrics.y_ppem << 6;
    
                /* only use strikes with valid PPEM values */
                if ( bsize->x_ppem && bsize->y_ppem )
                  sbit_strike_map[bsize_idx++] = strike_idx;
              }
    
              /* reduce array size to the actually used elements */
              FT_MEM_QRENEW_ARRAY( sbit_strike_map, count, bsize_idx );
    
              /* from now on, all strike indices are mapped */
              /* using `sbit_strike_map'                    */
              if ( bsize_idx )
              {
                face->sbit_strike_map = sbit_strike_map;
    
                root->face_flags     |= FT_FACE_FLAG_FIXED_SIZES;
                root->num_fixed_sizes = (FT_Int)bsize_idx;
              }
            }
          }
    
    #endif /* TT_CONFIG_OPTION_EMBEDDED_BITMAPS */
    
          /* a font with no bitmaps and no outlines is scalable; */
          /* it has only empty glyphs then                       */
          if ( !FT_HAS_FIXED_SIZES( root ) && !FT_IS_SCALABLE( root ) )
            root->face_flags |= FT_FACE_FLAG_SCALABLE;
    
    
          /**********************************************************************
           *
           * Set up metrics.
           */
          if ( FT_IS_SCALABLE( root ) ||
               FT_HAS_SBIX( root )    )
          {
            /* XXX What about if outline header is missing */
            /*     (e.g. sfnt wrapped bitmap)?             */
            root->bbox.xMin    = face->header.xMin;
            root->bbox.yMin    = face->header.yMin;
            root->bbox.xMax    = face->header.xMax;
            root->bbox.yMax    = face->header.yMax;
            root->units_per_EM = face->header.Units_Per_EM;
    
    
            /*
             * Computing the ascender/descender/height is tricky.
             *
             * The OpenType specification v1.8.3 says:
             *
             *   [OS/2's] sTypoAscender, sTypoDescender and sTypoLineGap fields
             *   are intended to allow applications to lay out documents in a
             *   typographically-correct and portable fashion.
             *
             * This is somewhat at odds with the decades of backwards
             * compatibility, operating systems and applications doing whatever
             * they want, not to mention broken fonts.
             *
             * Not all fonts have an OS/2 table; in this case, we take the values
             * in the horizontal header, although there is nothing stopping the
             * values from being unreliable. Even with a OS/2 table, certain fonts
             * set the sTypoAscender, sTypoDescender and sTypoLineGap fields to 0
             * and instead correctly set usWinAscent and usWinDescent.
             *
             * As an example, Arial Narrow is shipped as four files ARIALN.TTF,
             * ARIALNI.TTF, ARIALNB.TTF and ARIALNBI.TTF. Strangely, all fonts have
             * the same values in their sTypo* fields, except ARIALNB.ttf which
             * sets them to 0. All of them have different usWinAscent/Descent
             * values. The OS/2 table therefore cannot be trusted for computing the
             * text height reliably.
             *
             * As a compromise, do the following:
             *
             * 1. If the OS/2 table exists and the fsSelection bit 7 is set
             *    (USE_TYPO_METRICS), trust the font and use the sTypo* metrics.
             * 2. Otherwise, use the `hhea' table's metrics.
             * 3. If they are zero and the OS/2 table exists,
             *    1. use the OS/2 table's sTypo* metrics if they are non-zero.
             *    2. Otherwise, use the OS/2 table's usWin* metrics.
             */
    
            if ( face->os2.version != 0xFFFFU && face->os2.fsSelection & 128 )
            {
              root->ascender  = face->os2.sTypoAscender;
              root->descender = face->os2.sTypoDescender;
              root->height    = root->ascender - root->descender +
                                face->os2.sTypoLineGap;
            }
            else
            {
              root->ascender  = face->horizontal.Ascender;
              root->descender = face->horizontal.Descender;
              root->height    = root->ascender - root->descender +
                                face->horizontal.Line_Gap;
    
              if ( !( root->ascender || root->descender ) )
              {
                if ( face->os2.version != 0xFFFFU )
                {
                  if ( face->os2.sTypoAscender || face->os2.sTypoDescender )
                  {
                    root->ascender  = face->os2.sTypoAscender;
                    root->descender = face->os2.sTypoDescender;
                    root->height    = root->ascender - root->descender +
                                      face->os2.sTypoLineGap;
                  }
                  else
                  {
                    root->ascender  =  (FT_Short)face->os2.usWinAscent;
                    root->descender = -(FT_Short)face->os2.usWinDescent;
                    root->height    =  root->ascender - root->descender;
                  }
                }
              }
            }
    
            root->max_advance_width  =
              (FT_Short)face->horizontal.advance_Width_Max;
            root->max_advance_height =
              (FT_Short)( face->vertical_info ? face->vertical.advance_Height_Max
                                              : root->height );
    
            /* See https://www.microsoft.com/typography/otspec/post.htm -- */
            /* Adjust underline position from top edge to centre of        */
            /* stroke to convert TrueType meaning to FreeType meaning.     */
            root->underline_position  = face->postscript.underlinePosition -
                                        face->postscript.underlineThickness / 2;
            root->underline_thickness = face->postscript.underlineThickness;
          }
    
        }
    
      Exit:
        FT_TRACE2(( "sfnt_load_face: done\n" ));
    
        return error;
      }
    
    
    #undef LOAD_
    #undef LOADM_
    #undef GET_NAME
    
    
      FT_LOCAL_DEF( void )
      sfnt_done_face( TT_Face  face )
      {
        FT_Memory     memory;
        SFNT_Service  sfnt;
    
    
        if ( !face )
          return;
    
        memory = face->root.memory;
        sfnt   = (SFNT_Service)face->sfnt;
    
        if ( sfnt )
        {
          /* destroy the postscript names table if it is loaded */
          if ( sfnt->free_psnames )
            sfnt->free_psnames( face );
    
          /* destroy the embedded bitmaps table if it is loaded */
          if ( sfnt->free_eblc )
            sfnt->free_eblc( face );
    
          /* destroy color table data if it is loaded */
          if ( sfnt->free_cpal )
          {
            sfnt->free_cpal( face );
            sfnt->free_colr( face );
          }
    
    #ifdef FT_CONFIG_OPTION_SVG
          /* free SVG data */
          if ( sfnt->free_svg )
            sfnt->free_svg( face );
    #endif
        }
    
    #ifdef TT_CONFIG_OPTION_BDF
        /* freeing the embedded BDF properties */
        tt_face_free_bdf_props( face );
    #endif
    
        /* freeing the kerning table */
        tt_face_done_kern( face );
    
    #ifdef TT_CONFIG_OPTION_GPOS_KERNING
        /* freeing the GPOS table */
        tt_face_done_gpos( face );
    #endif
    
        /* freeing the collection table */
        FT_FREE( face->ttc_header.offsets );
        face->ttc_header.count = 0;
    
        /* freeing table directory */
        FT_FREE( face->dir_tables );
        face->num_tables = 0;
    
        {
          FT_Stream  stream = FT_FACE_STREAM( face );
    
    
          /* simply release the 'cmap' table frame */
          FT_FRAME_RELEASE( face->cmap_table );
          face->cmap_size = 0;
        }
    
        face->horz_metrics_size = 0;
        face->vert_metrics_size = 0;
    
        /* freeing vertical metrics, if any */
        if ( face->vertical_info )
        {
          FT_FREE( face->vertical.long_metrics  );
          FT_FREE( face->vertical.short_metrics );
          face->vertical_info = 0;
        }
    
        /* freeing the gasp table */
        FT_FREE( face->gasp.gaspRanges );
        face->gasp.numRanges = 0;
    
        /* freeing the name table */
        if ( sfnt )
          sfnt->free_name( face );
    
        /* freeing family and style name */
        FT_FREE( face->root.family_name );
        FT_FREE( face->root.style_name );
    
        /* freeing sbit size table */
        FT_FREE( face->root.available_sizes );
        FT_FREE( face->sbit_strike_map );
        face->root.num_fixed_sizes = 0;
    
        FT_FREE( face->postscript_name );
    
    #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
        FT_FREE( face->var_postscript_prefix );
        FT_FREE( face->non_var_style_name );
    #endif
    
        /* freeing glyph color palette data */
        FT_FREE( face->palette_data.palette_name_ids );
        FT_FREE( face->palette_data.palette_flags );
        FT_FREE( face->palette_data.palette_entry_name_ids );
        FT_FREE( face->palette );
    
        face->sfnt = NULL;
      }
    
    
    /* END */