/****************************************************************************
 *
 * ttmtx.c
 *
 *   Load the metrics tables common to TTF and OTF fonts (body).
 *
 * Copyright (C) 2006-2025 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */


#include <freetype/internal/ftdebug.h>
#include <freetype/internal/ftstream.h>
#include <freetype/tttags.h>

#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
#include <freetype/internal/services/svmetric.h>
#endif

#include "ttmtx.h"

#include "sferrors.h"


  /* IMPORTANT: The TT_HoriHeader and TT_VertHeader structures should   */
  /*            be identical except for the names of their fields,      */
  /*            which are different.                                    */
  /*                                                                    */
  /*            This ensures that `tt_face_load_hmtx' is able to read   */
  /*            both the horizontal and vertical headers.               */


  /**************************************************************************
   *
   * 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  ttmtx


  /**************************************************************************
   *
   * @Function:
   *   tt_face_load_hmtx
   *
   * @Description:
   *   Load the `hmtx' or `vmtx' table into a face object.
   *
   * @Input:
   *   face ::
   *     A handle to the target face object.
   *
   *   stream ::
   *     The input stream.
   *
   *   vertical ::
   *     A boolean flag.  If set, load `vmtx'.
   *
   * @Return:
   *   FreeType error code.  0 means success.
   */
  FT_LOCAL_DEF( FT_Error )
  tt_face_load_hmtx( TT_Face    face,
                     FT_Stream  stream,
                     FT_Bool    vertical )
  {
    FT_Error   error;
    FT_ULong   tag, table_size;
    FT_ULong*  ptable_offset;
    FT_ULong*  ptable_size;


    if ( vertical )
    {
      tag           = TTAG_vmtx;
      ptable_offset = &face->vert_metrics_offset;
      ptable_size   = &face->vert_metrics_size;
    }
    else
    {
      tag           = TTAG_hmtx;
      ptable_offset = &face->horz_metrics_offset;
      ptable_size   = &face->horz_metrics_size;
    }

    error = face->goto_table( face, tag, stream, &table_size );
    if ( error )
      goto Fail;

    *ptable_size   = table_size;
    *ptable_offset = FT_STREAM_POS();

  Fail:
    return error;
  }


  /**************************************************************************
   *
   * @Function:
   *   tt_face_load_hhea
   *
   * @Description:
   *   Load the `hhea' or 'vhea' table into a face object.
   *
   * @Input:
   *   face ::
   *     A handle to the target face object.
   *
   *   stream ::
   *     The input stream.
   *
   *   vertical ::
   *     A boolean flag.  If set, load `vhea'.
   *
   * @Return:
   *   FreeType error code.  0 means success.
   */
  FT_LOCAL_DEF( FT_Error )
  tt_face_load_hhea( TT_Face    face,
                     FT_Stream  stream,
                     FT_Bool    vertical )
  {
    FT_Error        error;
    TT_HoriHeader*  header;

    static const FT_Frame_Field  metrics_header_fields[] =
    {
#undef  FT_STRUCTURE
#define FT_STRUCTURE  TT_HoriHeader

      FT_FRAME_START( 36 ),
        FT_FRAME_ULONG ( Version ),
        FT_FRAME_SHORT ( Ascender ),
        FT_FRAME_SHORT ( Descender ),
        FT_FRAME_SHORT ( Line_Gap ),
        FT_FRAME_USHORT( advance_Width_Max ),
        FT_FRAME_SHORT ( min_Left_Side_Bearing ),
        FT_FRAME_SHORT ( min_Right_Side_Bearing ),
        FT_FRAME_SHORT ( xMax_Extent ),
        FT_FRAME_SHORT ( caret_Slope_Rise ),
        FT_FRAME_SHORT ( caret_Slope_Run ),
        FT_FRAME_SHORT ( caret_Offset ),
        FT_FRAME_SHORT ( Reserved[0] ),
        FT_FRAME_SHORT ( Reserved[1] ),
        FT_FRAME_SHORT ( Reserved[2] ),
        FT_FRAME_SHORT ( Reserved[3] ),
        FT_FRAME_SHORT ( metric_Data_Format ),
        FT_FRAME_USHORT( number_Of_HMetrics ),
      FT_FRAME_END
    };


    if ( vertical )
    {
      void  *v = &face->vertical;


      error = face->goto_table( face, TTAG_vhea, stream, 0 );
      if ( error )
        goto Fail;

      header = (TT_HoriHeader*)v;
    }
    else
    {
      error = face->goto_table( face, TTAG_hhea, stream, 0 );
      if ( error )
        goto Fail;

      header = &face->horizontal;
    }

    if ( FT_STREAM_READ_FIELDS( metrics_header_fields, header ) )
      goto Fail;

    FT_TRACE3(( "Ascender:          %5d\n", header->Ascender ));
    FT_TRACE3(( "Descender:         %5d\n", header->Descender ));
    FT_TRACE3(( "number_Of_Metrics: %5u\n", header->number_Of_HMetrics ));

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

  Fail:
    return error;
  }


  /**************************************************************************
   *
   * @Function:
   *   tt_face_get_metrics
   *
   * @Description:
   *   Return the horizontal or vertical metrics in font units for a
   *   given glyph.  The values are the left side bearing (top side
   *   bearing for vertical metrics) and advance width (advance height
   *   for vertical metrics).
   *
   * @Input:
   *   face ::
   *     A pointer to the TrueType face structure.
   *
   *   vertical ::
   *     If set to TRUE, get vertical metrics.
   *
   *   gindex ::
   *     The glyph index.
   *
   * @Output:
   *   abearing ::
   *     The bearing, either left side or top side.
   *
   *   aadvance ::
   *     The advance width or advance height, depending on
   *     the `vertical' flag.
   */
  FT_LOCAL_DEF( void )
  tt_face_get_metrics( TT_Face     face,
                       FT_Bool     vertical,
                       FT_UInt     gindex,
                       FT_Short   *abearing,
                       FT_UShort  *aadvance )
  {
    FT_Error        error;
    FT_Stream       stream = face->root.stream;
    TT_HoriHeader*  header;
    FT_ULong        table_pos, table_size, table_end;
    FT_UShort       k;

#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
    FT_Service_MetricsVariations  var =
      (FT_Service_MetricsVariations)face->tt_var;
#endif


    if ( vertical )
    {
      void*  v = &face->vertical;


      header     = (TT_HoriHeader*)v;
      table_pos  = face->vert_metrics_offset;
      table_size = face->vert_metrics_size;
    }
    else
    {
      header     = &face->horizontal;
      table_pos  = face->horz_metrics_offset;
      table_size = face->horz_metrics_size;
    }

    table_end = table_pos + table_size;

    k = header->number_Of_HMetrics;

    if ( k > 0 )
    {
      if ( gindex < (FT_UInt)k )
      {
        table_pos += 4 * gindex;
        if ( table_pos + 4 > table_end )
          goto NoData;

        if ( FT_STREAM_SEEK( table_pos ) ||
             FT_READ_USHORT( *aadvance ) ||
             FT_READ_SHORT( *abearing )  )
          goto NoData;
      }
      else
      {
        table_pos += 4 * ( k - 1 );
        if ( table_pos + 2 > table_end )
          goto NoData;

        if ( FT_STREAM_SEEK( table_pos ) ||
             FT_READ_USHORT( *aadvance ) )
          goto NoData;

        table_pos += 4 + 2 * ( gindex - k );
        if ( table_pos + 2 > table_end )
          *abearing = 0;
        else
        {
          if ( FT_STREAM_SEEK( table_pos ) )
            *abearing = 0;
          else
            (void)FT_READ_SHORT( *abearing );
        }
      }
    }
    else
    {
    NoData:
      *abearing = 0;
      *aadvance = 0;
    }

#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
    if ( var && FT_IS_VARIATION( &face->root ) )
    {
      FT_Face  f = FT_FACE( face );
      FT_Int   a = (FT_Int)*aadvance;
      FT_Int   b = (FT_Int)*abearing;


      if ( vertical )
      {
        if ( var->vadvance_adjust )
          var->vadvance_adjust( f, gindex, &a );
        if ( var->tsb_adjust )
          var->tsb_adjust( f, gindex, &b );
      }
      else
      {
        if ( var->hadvance_adjust )
          var->hadvance_adjust( f, gindex, &a );
        if ( var->lsb_adjust )
          var->lsb_adjust( f, gindex, &b );
      }

      *aadvance = (FT_UShort)a;
      *abearing = (FT_Short)b;
    }
#endif
  }


/* END */
