/*******************************************************************
 *
 *  ttload.c                                                    1.0
 *
 *    TrueType Tables Loader.
 *
 *  Copyright 1996-1998 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 "tttypes.h"
#include "ttdebug.h"
#include "ttcalc.h"
#include "ttfile.h"

#include "tttables.h"
#include "ttobjs.h"

#include "ttmemory.h"
#include "tttags.h"
#include "ttload.h"

/* required by the tracing mode */
#undef  TT_COMPONENT
#define TT_COMPONENT      trace_load

/* In all functions, the stream is taken from the 'face' object */
#define DEFINE_LOCALS           DEFINE_LOAD_LOCALS( face->stream )
#define DEFINE_LOCALS_WO_FRAME  DEFINE_LOAD_LOCALS_WO_FRAME( face->stream )


/*******************************************************************
 *
 *  Function    :  LookUp_TrueType_Table
 *
 *  Description :  Looks for a TrueType table by name.
 *
 *  Input  :  face       face table to look for
 *            tag        searched tag
 *
 *  Output :  Index of table if found, -1 otherwise.
 *
 ******************************************************************/

  EXPORT_FUNC
  Long  TT_LookUp_Table( PFace  face,
                         ULong  tag  )
  {
    UShort  i;


    PTRACE4(( "TT_LookUp_Table( %08lx, %c%c%c%c )\n",
                  (Long)face,
                  (Char)(tag >> 24),
                  (Char)(tag >> 16),
                  (Char)(tag >> 8),
                  (Char)(tag) ));

    for ( i = 0; i < face->numTables; i++ )
      if ( face->dirTables[i].Tag == tag )
        return i;

    PTRACE4(( "    Could not find table!\n" ));
    return -1;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Collection
 *
 *  Description :  Loads the TTC table directory into face table.
 *
 *  Input  :  face    face record to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  static TT_Error  Load_TrueType_Collection( PFace  face )
  {
    DEFINE_LOCALS;

    ULong  n;


    PTRACE3(( "Load_TrueType_Collection( %08lx )\n", (long)face ));

    if ( FILE_Seek   ( 0L ) ||
         ACCESS_Frame( 12L ) )
      return error;

    face->ttcHeader.Tag      = GET_Tag4();
    face->ttcHeader.version  = GET_Long();
    face->ttcHeader.DirCount = GET_Long();

    FORGET_Frame();

    if ( face->ttcHeader.Tag != TTAG_ttcf )
    {
      face->ttcHeader.Tag      = 0;
      face->ttcHeader.version  = 0;
      face->ttcHeader.DirCount = 0;

      face->ttcHeader.TableDirectory = NULL;

      PTRACE3(("skipped.\n"));

      return TT_Err_File_Is_Not_Collection;
    }

    if ( ALLOC_ARRAY( face->ttcHeader.TableDirectory,
                      face->ttcHeader.DirCount,
                      ULong )                         ||
         ACCESS_Frame( face->ttcHeader.DirCount * 4L ) )
      return error;

    for ( n = 0; n < face->ttcHeader.DirCount; n++ )
      face->ttcHeader.TableDirectory[n] = GET_ULong();

    FORGET_Frame();

    PTRACE3(( "collections directory loaded.\n" ));
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Directory
 *
 *  Description :  Loads the table directory into face table.
 *
 *  Input  :  face       face record to look for
 *
 *            faceIndex  the index of the TrueType font, when
 *                       we're opening a collection.
 *
 *  Output :  SUCCESS on success.  FAILURE on error.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Directory( PFace  face, ULong  faceIndex )
  {
    DEFINE_LOCALS;

    UShort     n, limit;
    TTableDir  tableDir;

    PTableDirEntry  entry;


    PTRACE2(("Load_TT_Directory( %08lx, %ld )\n", (long)face, faceIndex));

    error = Load_TrueType_Collection( face );

    if ( error )
    {
      if ( error != TT_Err_File_Is_Not_Collection )
        return error;

      /* the file isn't a collection, exit if we're asking */
      /* for a collected font                              */
      if ( faceIndex != 0 )
        return error;

      /* Now skip to the beginning of the file */
      if ( FILE_Seek( 0 ) )
        return error;
    }
    else
    {
      /* The file is a collection. Check the font index */
      if ( faceIndex >= face->ttcHeader.DirCount )
        return TT_Err_Bad_Argument;

      /* select a TrueType font in the ttc file   */
      if ( FILE_Seek( face->ttcHeader.TableDirectory[faceIndex] ) )
        return error;
    }

    if ( ACCESS_Frame( 12L ) )
      return error;

    tableDir.version   = GET_Long();
    tableDir.numTables = GET_UShort();

    tableDir.searchRange   = GET_UShort();
    tableDir.entrySelector = GET_UShort();
    tableDir.rangeShift    = GET_UShort();

    FORGET_Frame();

    PTRACE2(( "-- Tables count   : %12u\n", tableDir.numTables ));
    PTRACE2(( "-- Format version : %08lx\n", tableDir.version ));

    /* Check that we have a 'sfnt' format there */

    if ( tableDir.version != 0x00010000 )
    {
      PERROR(( "!! invalid file format" ));
      return TT_Err_Invalid_File_Format;
    }

    face->numTables = tableDir.numTables;

    if ( ALLOC_ARRAY( face->dirTables,
                      face->numTables,
                      TTableDirEntry ) )
      return error;

    if ( ACCESS_Frame( face->numTables * 16L ) )
      return error;

    limit = face->numTables;
    entry = face->dirTables;

    for ( n = 0; n < limit; n++ )
    {                      /* loop through the tables and get all entries */
      entry->Tag      = GET_Tag4();
      entry->CheckSum = GET_ULong();
      entry->Offset   = GET_Long();
      entry->Length   = GET_Long();

      PTRACE2(( "  %c%c%c%c  -  %08lx  -  %08lx\n",
                (Char)(entry->Tag >> 24),
                (Char)(entry->Tag >> 16),
                (Char)(entry->Tag >> 8 ),
                (Char)(entry->Tag),
                entry->Offset,
                entry->Length ));
      entry++;
    }

    FORGET_Frame();

    PTRACE2(( "Directory loaded\n\n" ));
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_MaxProfile
 *
 *  Description :  Loads the maxp table into face table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_MaxProfile( PFace  face )
  {
    DEFINE_LOCALS;

    Long         i;
    PMaxProfile  maxProfile = &face->maxProfile;


    PTRACE2(( "Load_TT_MaxProfile( %08lx )\n", (long)face ));

    if ( ( i = TT_LookUp_Table( face, TTAG_maxp ) ) < 0 )
      return TT_Err_Max_Profile_Missing;

    if ( FILE_Seek( face->dirTables[i].Offset ) )   /* seek to maxprofile */
      return error;

    if ( ACCESS_Frame( 32L ) )  /* read into frame */
      return error;

    /* read frame data into face table */
    maxProfile->version               = GET_ULong();

    maxProfile->numGlyphs             = GET_UShort();

    maxProfile->maxPoints             = GET_UShort();
    maxProfile->maxContours           = GET_UShort();
    maxProfile->maxCompositePoints    = GET_UShort();
    maxProfile->maxCompositeContours  = GET_UShort();

    maxProfile->maxZones              = GET_UShort();
    maxProfile->maxTwilightPoints     = GET_UShort();

    maxProfile->maxStorage            = GET_UShort();
    maxProfile->maxFunctionDefs       = GET_UShort();
    maxProfile->maxInstructionDefs    = GET_UShort();
    maxProfile->maxStackElements      = GET_UShort();
    maxProfile->maxSizeOfInstructions = GET_UShort();
    maxProfile->maxComponentElements  = GET_UShort();
    maxProfile->maxComponentDepth     = GET_UShort();

    FORGET_Frame();

    /* XXX : an adjustement that is necessary to load certain */
    /*       broken fonts like "Keystrokes MT" :-(            */
    /*                                                        */
    /*   We allocate 64 function entries by default when      */
    /*   the maxFunctionDefs field is null.                   */

    if (maxProfile->maxFunctionDefs == 0)
      maxProfile->maxFunctionDefs = 64;

    face->numGlyphs     = maxProfile->numGlyphs;

    face->maxPoints     = MAX( maxProfile->maxCompositePoints,
                               maxProfile->maxPoints );
    face->maxContours   = MAX( maxProfile->maxCompositeContours,
                               maxProfile->maxContours );
    face->maxComponents = maxProfile->maxComponentElements +
                          maxProfile->maxComponentDepth;

    PTRACE2(( "GASP loaded.\n" ));
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Gasp
 *
 *  Description :  Loads the TrueType Gasp table into the face
 *                 table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Gasp( PFace  face )
  {
    DEFINE_LOCALS;

    Long        i;
    UShort      j;
    TGasp*      gas;
    GaspRange*  gaspranges;


    PTRACE2(( "Load_TT_Gasp( %08lx )\n", (long)face ));

    if ( ( i = TT_LookUp_Table( face, TTAG_gasp ) ) < 0 )
      return TT_Err_Ok; /* gasp table is not required */

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

    gas = &face->gasp;

    gas->version   = GET_UShort();
    gas->numRanges = GET_UShort();

    FORGET_Frame();

    PTRACE3(( "number of ranges = %d\n", gas->numRanges ));

    if ( ALLOC_ARRAY( gaspranges, gas->numRanges, GaspRange ) ||
         ACCESS_Frame( gas->numRanges * 4L ) )
      goto Fail;

    face->gasp.gaspRanges = gaspranges;

    for ( j = 0; j < gas->numRanges; j++ )
    {
      gaspranges[j].maxPPEM  = GET_UShort();
      gaspranges[j].gaspFlag = GET_UShort();

      PTRACE3(( " [max:%d flag:%d]",
                gaspranges[j].maxPPEM,
                gaspranges[j].gaspFlag ));
    }
    PTRACE3(("\n"));

    FORGET_Frame();

    PTRACE2(( "GASP loaded\n" ));
    return TT_Err_Ok;

  Fail:
    FREE( gaspranges );
    gas->numRanges = 0;
    return error;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Header
 *
 *  Description :  Loads the TrueType header table into the face
 *                 table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Header( PFace  face )
  {
    DEFINE_LOCALS;

    Long        i;
    TT_Header*  header;


    PTRACE2(( "Load_TT_Header( %08lx )\n", (long)face ));

    if ( ( i = TT_LookUp_Table( face, TTAG_head ) ) < 0 )
    {
      PTRACE0(( "Font Header is missing !!\n" ));
      return TT_Err_Header_Table_Missing;
    }

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

    header = &face->fontHeader;

    header->Table_Version = GET_ULong();
    header->Font_Revision = GET_ULong();

    header->CheckSum_Adjust = GET_Long();
    header->Magic_Number    = GET_Long();

    header->Flags        = GET_UShort();
    header->Units_Per_EM = GET_UShort();

    header->Created [0] = GET_Long();
    header->Created [1] = GET_Long();
    header->Modified[0] = GET_Long();
    header->Modified[1] = GET_Long();

    header->xMin = GET_Short();
    header->yMin = GET_Short();
    header->xMax = GET_Short();
    header->yMax = GET_Short();

    header->Mac_Style       = GET_UShort();
    header->Lowest_Rec_PPEM = GET_UShort();

    header->Font_Direction      = GET_Short();
    header->Index_To_Loc_Format = GET_Short();
    header->Glyph_Data_Format   = GET_Short();

    FORGET_Frame();

    PTRACE2(( "    Units per EM : %8u\n", header->Units_Per_EM ));
    PTRACE2(( "    IndexToLoc   : %8d\n", header->Index_To_Loc_Format ));
    PTRACE2(( "Font Header Loaded.\n" ));
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Metrics
 *
 *  Description :  Loads the horizontal or vertical metrics table
 *                 into face object.
 *
 *  Input  :  face
 *            vertical   set to true when loading the vmtx table,
 *                       or false for hmtx
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  static
  TT_Error  Load_TrueType_Metrics( PFace  face,
                                   Bool   vertical )
  {
    DEFINE_LOCALS;

    Long              n, num_shorts, num_longs;

    PLongMetrics*     longs;
    PShortMetrics*    shorts;

    PLongMetrics      long_metric;

    PTRACE2(( "Load_TT_%s_Metrics( %08lx )\n",
              vertical ? "Vertical" : "Horizontal",
              (long)face ));

    if ( vertical )
    {
      /* The table is optional, quit silently if it wasn't found       */
      /* XXX : Some fonts have a valid vertical header with a non-null */
      /*       "number_of_VMetrics" fields, but no corresponding       */
      /*       'vmtx' table to get the metrics from (e.g. mingliu)     */
      /*                                                               */
      /*       For safety, we set the field to 0 !                     */
      /*                                                               */
      n = TT_LookUp_Table( face, TTAG_vmtx );
      if ( n < 0 )
      {
        /* Set the number_Of_VMetrics to 0! */
        PTRACE2(( "  no vertical header in file.\n" ));
        face->verticalHeader.number_Of_VMetrics = 0;
        return TT_Err_Ok;
      }

      num_longs = face->verticalHeader.number_Of_VMetrics;
      longs     = (PLongMetrics*)&face->verticalHeader.long_metrics;
      shorts    = (PShortMetrics*)&face->verticalHeader.short_metrics;
    }
    else
    {
      if ( ( n = TT_LookUp_Table( face, TTAG_hmtx ) ) < 0 )
      {
        PERROR(( "!! No Horizontal metrics in file !!\n" ));
        return TT_Err_Hmtx_Table_Missing;
      }

      num_longs = face->horizontalHeader.number_Of_HMetrics;
      longs     = (PLongMetrics*)&face->horizontalHeader.long_metrics;
      shorts    = (PShortMetrics*)&face->horizontalHeader.short_metrics;
    }

    num_shorts = face->maxProfile.numGlyphs - num_longs;

    if ( num_shorts < 0 )            /* sanity check */
    {
      PERROR(( "!! more metrics than glyphs!\n" ));
      return TT_Err_Invalid_Horiz_Metrics;
    }


    if ( ALLOC_ARRAY( *longs,  num_longs,  TLongMetrics  ) ||
         ALLOC_ARRAY( *shorts, num_shorts, TShortMetrics ) )
      return error;

    if ( FILE_Seek( face->dirTables[n].Offset )   ||
         ACCESS_Frame( face->dirTables[n].Length ) )
      return error;

    long_metric = *longs;
    for ( n = 0; n < num_longs; n++ )
    {
      long_metric->advance = GET_UShort();
      long_metric->bearing = GET_Short();
      long_metric++;
    }

    for ( n = 0; n < num_shorts; n++ )
      (*shorts)[n] = GET_Short();

    FORGET_Frame();

    PTRACE2(( "loaded\n" ));
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    : Load_TrueType_Metrics_Header
 *
 *  Description : Loads either the "hhea" or "vhea" table in memory
 *
 *  Input  :  face       face table to look for
 *            vertical   a boolean.  When set, queries the optional
 *                       "vhea" table.  Otherwise, load the mandatory
 *                       "hhea" horizontal header.
 *
 *  Output :  Error code.
 *
 *  Note : This function now loads the corresponding metrics table
 *         (either hmtx or vmtx) and attaches it to the header.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Metrics_Header( PFace  face,
                                          Bool   vertical )
  {
    DEFINE_LOCALS;

    Long  i;

    TT_Horizontal_Header*  header;


    PTRACE2(( vertical ? "Vertical header" : "Horizontal header " ));

    if ( vertical )
    {
      face->verticalInfo = 0;

      /* The vertical header table is optional, so return quietly if */
      /* we don't find it..                                          */
      if ( ( i = TT_LookUp_Table( face, TTAG_vhea ) ) < 0 )
        return TT_Err_Ok;

      face->verticalInfo = 1;
      header = (TT_Horizontal_Header*)&face->verticalHeader;
    }
    else
    {
      /* The orizontal header is mandatory, return an error if we */
      /* don't find it.                                           */
      if ( ( i = TT_LookUp_Table( face, TTAG_hhea ) ) < 0 )
        return TT_Err_Horiz_Header_Missing;

      header = &face->horizontalHeader;
    }

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

    header->Version   = GET_ULong();
    header->Ascender  = GET_Short();
    header->Descender = GET_Short();
    header->Line_Gap  = GET_Short();

    header->advance_Width_Max = GET_UShort();

    header->min_Left_Side_Bearing  = GET_Short();
    header->min_Right_Side_Bearing = GET_Short();
    header->xMax_Extent            = GET_Short();
    header->caret_Slope_Rise       = GET_Short();
    header->caret_Slope_Run        = GET_Short();

    header->Reserved[0] = GET_Short();
    header->Reserved[1] = GET_Short();
    header->Reserved[2] = GET_Short();
    header->Reserved[3] = GET_Short();
    header->Reserved[4] = GET_Short();

    header->metric_Data_Format = GET_Short();
    header->number_Of_HMetrics = GET_UShort();

    FORGET_Frame();

    header->long_metrics  = NULL;
    header->short_metrics = NULL;

    PTRACE2(( "loaded\n" ));

    /* Now try to load the corresponding metrics */

    return Load_TrueType_Metrics( face, vertical );
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Locations
 *
 *  Description :  Loads the location table into face table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 *  NOTE:
 *    The Font Header *must* be loaded in the leading segment
 *    calling this function.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Locations( PFace  face )
  {
    DEFINE_LOCALS;

    Long   n, limit;
    Short  LongOffsets;


    PTRACE2(( "Locations " ));

    LongOffsets = face->fontHeader.Index_To_Loc_Format;

    if ( ( n = TT_LookUp_Table( face, TTAG_loca ) ) < 0 )
      return TT_Err_Locations_Missing;

    if ( FILE_Seek( face->dirTables[n].Offset ) )
      return error;

    if ( LongOffsets != 0 )
    {
      face->numLocations = face->dirTables[n].Length >> 2;

      PTRACE2(( "(32 bits offsets): %12d ",
                   face->numLocations ));

      if ( ALLOC_ARRAY( face->glyphLocations,
                        face->numLocations,
                        Long ) )
        return error;

      if ( ACCESS_Frame( face->numLocations * 4L ) )
        return error;

      limit = face->numLocations;

      for ( n = 0; n < limit; n++ )
        face->glyphLocations[n] = GET_Long();

      FORGET_Frame();
    }
    else
    {
      face->numLocations = face->dirTables[n].Length >> 1;

      PTRACE2(( "(16 bits offsets): %12d ",
                   face->numLocations ));

      if ( ALLOC_ARRAY( face->glyphLocations,
                        face->numLocations,
                        Long ) )
        return error;

      if ( ACCESS_Frame( face->numLocations * 2L ) )
        return error;

      limit = face->numLocations;

      for ( n = 0; n < limit; n++ )
        face->glyphLocations[n] =
          (Long)((ULong)GET_UShort() * 2);

      FORGET_Frame();
    }

    PTRACE2(( "loaded\n" ));

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Names
 *
 *  Description :  Loads the name table into face table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Names( PFace  face )
  {
    DEFINE_LOCALS;

    UShort  i, bytes;
    Long    n;
    PByte   storage;

    TName_Table*  names;
    TNameRec*     namerec;


    PTRACE2(( "Names " ));

    if ( ( n = TT_LookUp_Table( face, TTAG_name ) ) < 0 )
    {
      /* The name table is required so indicate failure. */
      PTRACE2(( "is missing!\n" ));

      return TT_Err_Name_Table_Missing;
    }

    /* Seek to the beginning of the table and check the frame access. */
    /* The names table has a 6 byte header.                           */
    if ( FILE_Seek( face->dirTables[n].Offset ) ||
         ACCESS_Frame( 6L ) )
      return error;

    names = &face->nameTable;

    /* Load the initial names data. */
    names->format         = GET_UShort();
    names->numNameRecords = GET_UShort();
    names->storageOffset  = GET_UShort();

    FORGET_Frame();

    /* Allocate the array of name records. */
    if ( ALLOC_ARRAY( names->names,
                      names->numNameRecords,
                      TNameRec )                    ||
         ACCESS_Frame( names->numNameRecords * 12L ) )
    {
      names->numNameRecords = 0;
      goto Fail;
    }

    /* Load the name records and determine how much storage is needed */
    /* to hold the strings themselves.                                */

    for ( i = bytes = 0; i < names->numNameRecords; i++ )
    {
      namerec = names->names + i;
      namerec->platformID   = GET_UShort();
      namerec->encodingID   = GET_UShort();
      namerec->languageID   = GET_UShort();
      namerec->nameID       = GET_UShort();
      namerec->stringLength = GET_UShort();
      namerec->stringOffset = GET_UShort();

#if 0
      /* check the ids */
      if ( namerec->platformID <= 3 )
      {
#endif
        /* this test takes care of 'holes' in the names tables, as */
        /* reported by Erwin                                       */
        if ( (namerec->stringOffset + namerec->stringLength) > bytes )
          bytes = namerec->stringOffset + namerec->stringLength;
#if 0
      }
#endif
    }

    FORGET_Frame();

    /* Allocate storage for the strings if they exist. */

    names->storage = NULL;

    if ( bytes > 0 )
    {
      if ( ALLOC( storage, bytes ) ||
           FILE_Read_At( face->dirTables[n].Offset + names->storageOffset,
                         (void*)storage,
                         bytes ) )
        goto Fail_Storage;

      names->storage = storage;

      /* Go through and assign the string pointers to the name records. */

      for ( i = 0; i < names->numNameRecords; i++ )
      {
        namerec = names->names + i;
        namerec->string = storage + names->names[i].stringOffset;

/* It is possible (but rather unlikely) that a new platform ID will be */
/* added by Apple, so we can't rule out IDs > 3.                       */

#if 0
        if ( namerec->platformID <= 3 )
          namerec->string = storage + names->names[i].stringOffset;
        else
        {
          namerec->string       = NULL;
          namerec->stringLength = 0;
        }
#endif
      }
    }

#ifdef DEBUG

    for ( i = 0; i < names->numNameRecords; i++ )
    {
      int  j;


      PTRACE2(( "%d %d %x %d ",
                   names->names[i].platformID,
                   names->names[i].encodingID,
                   names->names[i].languageID,
                   names->names[i].nameID ));

      /* I know that M$ encoded strings are Unicode,            */
      /* but this works reasonable well for debugging purposes. */
      for ( j = 0; j < names->names[i].stringLength; j++ )
      {
        if (names->names[i].string)
        {
          Char  c = *(names->names[i].string + j);


          if ( (Byte)c < 128 )
            PTRACE2(( "%c", c ));
        }
      }

      PTRACE2(( "\n" ));
    }

#endif

    PTRACE2(( "loaded\n" ));
    return TT_Err_Ok;

  Fail_Storage:
    FREE( storage );

  Fail:
    Free_TrueType_Names( face );
    return error;
  }


/*******************************************************************
 *
 *  Function    :  Free_TrueType_Names
 *
 *  Description :  Frees a name table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  TT_Err_Ok.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Free_TrueType_Names( PFace  face )
  {
    TName_Table*  names = &face->nameTable;


    /* free strings table */
    FREE( names->names );

    /* free strings storage */
    FREE( names->storage );

    names->numNameRecords = 0;
    names->format         = 0;
    names->storageOffset  = 0;

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_CVT
 *
 *  Description :  Loads cvt table into resident table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_CVT( PFace  face )
  {
    DEFINE_LOCALS;

    Long  n, limit;


    PTRACE2(( "CVT " ));

    if ( ( n = TT_LookUp_Table( face, TTAG_cvt ) ) < 0 )
    {
      PTRACE2(( "is missing!\n" ));

      face->cvtSize = 0;
      face->cvt     = NULL;
      return TT_Err_Ok;
    }

    face->cvtSize = face->dirTables[n].Length / 2;

    if ( ALLOC_ARRAY( face->cvt,
                      face->cvtSize,
                      Short ) )
      return error;

    if ( FILE_Seek( face->dirTables[n].Offset ) ||
         ACCESS_Frame( face->cvtSize * 2L ) )
      return error;

    limit = face->cvtSize;

    for ( n = 0; n < limit; n++ )
      face->cvt[n] = GET_Short();

    FORGET_Frame();

    PTRACE2(( "loaded\n" ));

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_CMap
 *
 *  Description :  Loads the cmap directory in memory.
 *                 The cmaps themselves are loaded in ttcmap.c .
 *
 *  Input  :  face
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_CMap( PFace  face )
  {
    DEFINE_LOCALS;

    Long  off, table_start;
    Long  n, limit;

    TCMapDir       cmap_dir;
    TCMapDirEntry  entry_;
    PCMapTable     cmap;


    PTRACE2(( "CMaps " ));

    if ( ( n = TT_LookUp_Table( face, TTAG_cmap ) ) < 0 )
      return TT_Err_CMap_Table_Missing;

    table_start = face->dirTables[n].Offset;

    if ( ( FILE_Seek( table_start ) ) ||
         ( ACCESS_Frame( 4L ) ) )           /* 4 bytes cmap header */
      return error;

    cmap_dir.tableVersionNumber = GET_UShort();
    cmap_dir.numCMaps           = GET_UShort();

    FORGET_Frame();

    off = FILE_Pos();  /* save offset to cmapdir[] which follows */

    /* save space in face table for cmap tables */
    if ( ALLOC_ARRAY( face->cMaps,
                      cmap_dir.numCMaps,
                      TCMapTable ) )
      return error;

    face->numCMaps = cmap_dir.numCMaps;

    limit = face->numCMaps;
    cmap  = face->cMaps;

    for ( n = 0; n < limit; n++ )
    {
      if ( FILE_Seek( off )  ||
           ACCESS_Frame( 8L ) )
        return error;

      /* extra code using entry_ for platxxx could be cleaned up later */
      cmap->loaded             = FALSE;
      cmap->platformID         = entry_.platformID         = GET_UShort();
      cmap->platformEncodingID = entry_.platformEncodingID = GET_UShort();

      entry_.offset = GET_Long();

      FORGET_Frame();

      off = FILE_Pos();

      if ( FILE_Seek( table_start + entry_.offset ) ||
           ACCESS_Frame( 6L ) )
        return error;

      cmap->format  = GET_UShort();
      cmap->length  = GET_UShort();
      cmap->version = GET_UShort();

      FORGET_Frame();

      cmap->offset = FILE_Pos();

      cmap++;
    }

    PTRACE2(( "loaded\n" ));

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Programs
 *
 *  Description :  Loads the font (fpgm) and cvt programs into the
 *                 face table.
 *
 *  Input  :  face
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Programs( PFace  face )
  {
    DEFINE_LOCALS_WO_FRAME;

    Long  n;


    PTRACE2(( "Font program " ));

    /* The font program is optional */
    if ( ( n = TT_LookUp_Table( face, TTAG_fpgm ) ) < 0 )
    {
      face->fontProgram = NULL;
      face->fontPgmSize = 0;

      PTRACE2(( "is missing!\n" ));
    }
    else
    {
      face->fontPgmSize = face->dirTables[n].Length;

      if ( ALLOC( face->fontProgram,
                  face->fontPgmSize )              ||
           FILE_Read_At( face->dirTables[n].Offset,
                         (void*)face->fontProgram,
                         face->fontPgmSize )       )
        return error;

      PTRACE2(( "loaded, %12d bytes\n", face->fontPgmSize ));
    }

    PTRACE2(( "Prep program " ));

    if ( ( n = TT_LookUp_Table( face, TTAG_prep ) ) < 0 )
    {
      face->cvtProgram = NULL;
      face->cvtPgmSize = 0;

      PTRACE2(( "is missing!\n" ));
    }
    else
    {
      face->cvtPgmSize = face->dirTables[n].Length;

      if ( ALLOC( face->cvtProgram,
                  face->cvtPgmSize )               ||
           FILE_Read_At( face->dirTables[n].Offset,
                         (void*)face->cvtProgram,
                         face->cvtPgmSize )        )
        return error;

      PTRACE2(( "loaded, %12d bytes\n", face->cvtPgmSize ));
    }

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_OS2
 *
 *  Description :  Loads the OS2 Table.
 *
 *  Input  :  face
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_OS2( PFace  face )
  {
    DEFINE_LOCALS;

    Long     i;
    TT_OS2*  os2;


    PTRACE2(( "OS/2 Table " ));

    if ( ( i = TT_LookUp_Table( face, TTAG_OS2 ) ) < 0 )
    {
      PTRACE2(( "is missing\n!" ));
      return TT_Err_OS2_Table_Missing;
    }

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

    os2 = &face->os2;

    os2->version             = GET_UShort();
    os2->xAvgCharWidth       = GET_Short();
    os2->usWeightClass       = GET_UShort();
    os2->usWidthClass        = GET_UShort();
    os2->fsType              = GET_Short();
    os2->ySubscriptXSize     = GET_Short();
    os2->ySubscriptYSize     = GET_Short();
    os2->ySubscriptXOffset   = GET_Short();
    os2->ySubscriptYOffset   = GET_Short();
    os2->ySuperscriptXSize   = GET_Short();
    os2->ySuperscriptYSize   = GET_Short();
    os2->ySuperscriptXOffset = GET_Short();
    os2->ySuperscriptYOffset = GET_Short();
    os2->yStrikeoutSize      = GET_Short();
    os2->yStrikeoutPosition  = GET_Short();
    os2->sFamilyClass        = GET_Short();

    for ( i = 0; i < 10; i++ )
      os2->panose[i] = GET_Byte();

    os2->ulUnicodeRange1     = GET_ULong();
    os2->ulUnicodeRange2     = GET_ULong();
    os2->ulUnicodeRange3     = GET_ULong();
    os2->ulUnicodeRange4     = GET_ULong();

    for ( i = 0; i < 4; i++ )
      os2->achVendID[i] = GET_Byte();

    os2->fsSelection         = GET_UShort();
    os2->usFirstCharIndex    = GET_UShort();
    os2->usLastCharIndex     = GET_UShort();
    os2->sTypoAscender       = GET_Short();
    os2->sTypoDescender      = GET_Short();
    os2->sTypoLineGap        = GET_Short();
    os2->usWinAscent         = GET_UShort();
    os2->usWinDescent        = GET_UShort();

    FORGET_Frame();

    if ( os2->version >= 0x0001 )
    {
      /* only version 1 tables */

      if ( ACCESS_Frame( 8L ) )  /* read into frame */
        return error;

      os2->ulCodePageRange1 = GET_ULong();
      os2->ulCodePageRange2 = GET_ULong();

      FORGET_Frame();
    }
    else
    {
      os2->ulCodePageRange1 = 0;
      os2->ulCodePageRange2 = 0;
    }

    PTRACE2(( "loaded\n" ));

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_PostScript
 *
 *  Description :  Loads the post table into face table.
 *
 *  Input  :  face         face table to look for
 *
 *  Output :  SUCCESS on success.  FAILURE on error.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_PostScript( PFace  face )
  {
    DEFINE_LOCALS;

    Long  i;

    TT_Postscript*  post = &face->postscript;


    PTRACE2(( "PostScript " ));

    if ( ( i = TT_LookUp_Table( face, TTAG_post ) ) < 0 )
      return TT_Err_Post_Table_Missing;

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

    /* read frame data into face table */

    post->FormatType         = GET_ULong();
    post->italicAngle        = GET_ULong();
    post->underlinePosition  = GET_Short();
    post->underlineThickness = GET_Short();
    post->isFixedPitch       = GET_ULong();
    post->minMemType42       = GET_ULong();
    post->maxMemType42       = GET_ULong();
    post->minMemType1        = GET_ULong();
    post->maxMemType1        = GET_ULong();

    FORGET_Frame();

    /* we don't load the glyph names, we do that in a */
    /* library extension (ftxpost).                   */

    PTRACE2(( "loaded\n" ));

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Hdmx
 *
 *  Description :  Loads the horizontal device metrics table.
 *
 *  Input  :  face         face object to look for
 *
 *  Output :  SUCCESS on success.  FAILURE on error.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Hdmx( PFace  face )
  {
    DEFINE_LOCALS;

    TT_Hdmx_Record*  rec;
    TT_Hdmx          hdmx;
    Long             table;
    UShort           n, num_glyphs;
    Long             record_size;


    hdmx.version     = 0;
    hdmx.num_records = 0;
    hdmx.records     = 0;

    face->hdmx = hdmx;

    if ( ( table = TT_LookUp_Table( face, TTAG_hdmx ) ) < 0 )
      return TT_Err_Ok;

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

    hdmx.version     = GET_UShort();
    hdmx.num_records = GET_Short();
    record_size      = GET_Long();

    FORGET_Frame();

    /* Only recognize format 0 */

    if ( hdmx.version != 0 )
      return TT_Err_Ok;

    if ( ALLOC( hdmx.records, sizeof ( TT_Hdmx_Record ) * hdmx.num_records ) )
      return error;

    num_glyphs   = face->numGlyphs;
    record_size -= num_glyphs+2;
    rec          = hdmx.records;

    for ( n = 0; n < hdmx.num_records; n++ )
    {
      /* read record */

      if ( ACCESS_Frame( 2L ) )
        goto Fail;

      rec->ppem      = GET_Byte();
      rec->max_width = GET_Byte();

      FORGET_Frame();

      if ( ALLOC( rec->widths, num_glyphs )  ||
           FILE_Read( rec->widths, num_glyphs ) )
        goto Fail;

      /* skip padding bytes */
      if ( record_size > 0 )
        if ( FILE_Skip( record_size ) )
          goto Fail;

      rec++;
    }

    face->hdmx = hdmx;

    return TT_Err_Ok;

  Fail:
    for ( n = 0; n < hdmx.num_records; n++ )
      FREE( hdmx.records[n].widths );

    FREE( hdmx.records );
    return error;
  }


/*******************************************************************
 *
 *  Function    :  Free_TrueType_Hdmx
 *
 *  Description :  Frees the horizontal device metrics table.
 *
 *  Input  :  face         face object to look for
 *
 *  Output :  TT_Err_Ok.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Free_TrueType_Hdmx( PFace  face )
  {
    UShort  n;


    if ( !face )
      return TT_Err_Ok;

    for ( n = 0; n < face->hdmx.num_records; n++ )
      FREE( face->hdmx.records[n].widths );

    FREE( face->hdmx.records );
    face->hdmx.num_records = 0;

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Load_TrueType_Any
 *
 *  Description :  Loads any font table in clinet memory. Used by
 *                 the TT_Get_Font_Data API
 *
 *  Input  :  face     face object to look for
 *
 *            tag      tag of table to load. Use the value 0 if you
 *                     want to access the whole font file, else set
 *                     this parameter to a valid TrueType table tag
 *                     that you can forge with the MAKE_TT_TAG
 *                     macro.
 *
 *            offset   starting offset in the table (or the file
 *                     if tag == 0 )
 *
 *            buffer   address of target buffer
 *
 *            length   address of decision variable :
 *
 *                       if length == NULL :
 *                             load the whole table. returns an
 *                             an error if 'offset' == 0 !!
 *
 *                       if *length == 0 :
 *                             exit immediately, returning the
 *                             length of the given table, or of
 *                             the font file, depending on the
 *                             value of 'tag'
 *
 *                       if *length != 0 :
 *                             load the next 'length' bytes of
 *                             table or font, starting at offset
 *                             'offset' (in table or font too).
 *
 *  Output :  Error condition
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Load_TrueType_Any( PFace  face,
                               ULong  tag,
                               Long   offset,
                               void*  buffer,
                               Long*  length )
  {
    TT_Stream  stream;
    TT_Error   error;
    Long       table;
    ULong      size;


    if ( tag != 0 )
    {
      /* look for tag in font directory */
      table = TT_LookUp_Table( face, tag );
      if ( table < 0 )
        return TT_Err_Table_Missing;

      offset += face->dirTables[table].Offset;
      size    = face->dirTables[table].Length;
    }
    else
      /* tag = 0 -- the use want to access the font file directly */
      size = TT_Stream_Size( face->stream );

    if ( length && *length == 0 )
    {
      *length = size;
      return TT_Err_Ok;
    }

    if ( length )
      size = *length;

    TT_Use_Stream( face->stream, &stream );
    error = FILE_Read_At( offset, buffer, size );
    TT_Done_Stream( &stream );

    return error;
  }


/* END */
