/****************************************************************************
 *
 * pfrobjs.c
 *
 *   FreeType PFR object methods (body).
 *
 * Copyright (C) 2002-2021 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 "pfrobjs.h"
#include "pfrload.h"
#include "pfrgload.h"
#include "pfrcmap.h"
#include "pfrsbit.h"
#include <freetype/ftoutln.h>
#include <freetype/internal/ftdebug.h>
#include <freetype/internal/ftcalc.h>
#include <freetype/ttnameid.h>

#include "pfrerror.h"

#undef  FT_COMPONENT
#define FT_COMPONENT  pfr


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                     FACE OBJECT METHODS                       *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL_DEF( void )
  pfr_face_done( FT_Face  pfrface )     /* PFR_Face */
  {
    PFR_Face   face = (PFR_Face)pfrface;
    FT_Memory  memory;


    if ( !face )
      return;

    memory = pfrface->driver->root.memory;

    /* we don't want dangling pointers */
    pfrface->family_name = NULL;
    pfrface->style_name  = NULL;

    /* finalize the physical font record */
    pfr_phy_font_done( &face->phy_font, FT_FACE_MEMORY( face ) );

    /* no need to finalize the logical font or the header */
    FT_FREE( pfrface->available_sizes );
  }


  FT_LOCAL_DEF( FT_Error )
  pfr_face_init( FT_Stream      stream,
                 FT_Face        pfrface,
                 FT_Int         face_index,
                 FT_Int         num_params,
                 FT_Parameter*  params )
  {
    PFR_Face  face = (PFR_Face)pfrface;
    FT_Error  error;

    FT_UNUSED( num_params );
    FT_UNUSED( params );


    FT_TRACE2(( "PFR driver\n" ));

    /* load the header and check it */
    error = pfr_header_load( &face->header, stream );
    if ( error )
      goto Exit;

    if ( !pfr_header_check( &face->header ) )
    {
      FT_TRACE2(( "  not a PFR font\n" ));
      error = FT_THROW( Unknown_File_Format );
      goto Exit;
    }

    /* check face index */
    {
      FT_Long  num_faces;


      error = pfr_log_font_count( stream,
                                  face->header.log_dir_offset,
                                  &num_faces );
      if ( error )
        goto Exit;

      pfrface->num_faces = num_faces;
    }

    if ( face_index < 0 )
      goto Exit;

    if ( ( face_index & 0xFFFF ) >= pfrface->num_faces )
    {
      FT_ERROR(( "pfr_face_init: invalid face index\n" ));
      error = FT_THROW( Invalid_Argument );
      goto Exit;
    }

    /* load the face */
    error = pfr_log_font_load(
              &face->log_font,
              stream,
              (FT_UInt)( face_index & 0xFFFF ),
              face->header.log_dir_offset,
              FT_BOOL( face->header.phy_font_max_size_high ) );
    if ( error )
      goto Exit;

    /* now load the physical font descriptor */
    error = pfr_phy_font_load( &face->phy_font, stream,
                               face->log_font.phys_offset,
                               face->log_font.phys_size );
    if ( error )
      goto Exit;

    /* now set up all root face fields */
    {
      PFR_PhyFont  phy_font = &face->phy_font;


      pfrface->face_index = face_index & 0xFFFF;
      pfrface->num_glyphs = (FT_Long)phy_font->num_chars + 1;

      pfrface->face_flags |= FT_FACE_FLAG_SCALABLE;

      /* if gps_offset == 0 for all characters, we  */
      /* assume that the font only contains bitmaps */
      {
        FT_UInt  nn;


        for ( nn = 0; nn < phy_font->num_chars; nn++ )
          if ( phy_font->chars[nn].gps_offset != 0 )
            break;

        if ( nn == phy_font->num_chars )
        {
          if ( phy_font->num_strikes > 0 )
            pfrface->face_flags = 0;        /* not scalable */
          else
          {
            FT_ERROR(( "pfr_face_init: font doesn't contain glyphs\n" ));
            error = FT_THROW( Invalid_File_Format );
            goto Exit;
          }
        }
      }

      if ( ( phy_font->flags & PFR_PHY_PROPORTIONAL ) == 0 )
        pfrface->face_flags |= FT_FACE_FLAG_FIXED_WIDTH;

      if ( phy_font->flags & PFR_PHY_VERTICAL )
        pfrface->face_flags |= FT_FACE_FLAG_VERTICAL;
      else
        pfrface->face_flags |= FT_FACE_FLAG_HORIZONTAL;

      if ( phy_font->num_strikes > 0 )
        pfrface->face_flags |= FT_FACE_FLAG_FIXED_SIZES;

      if ( phy_font->num_kern_pairs > 0 )
        pfrface->face_flags |= FT_FACE_FLAG_KERNING;

      /* If no family name was found in the `undocumented' auxiliary
       * data, use the font ID instead.  This sucks but is better than
       * nothing.
       */
      pfrface->family_name = phy_font->family_name;
      if ( !pfrface->family_name )
        pfrface->family_name = phy_font->font_id;

      /* note that the style name can be NULL in certain PFR fonts,
       * probably meaning `Regular'
       */
      pfrface->style_name = phy_font->style_name;

      pfrface->num_fixed_sizes = 0;
      pfrface->available_sizes = NULL;

      pfrface->bbox         = phy_font->bbox;
      pfrface->units_per_EM = (FT_UShort)phy_font->outline_resolution;
      pfrface->ascender     = (FT_Short) phy_font->bbox.yMax;
      pfrface->descender    = (FT_Short) phy_font->bbox.yMin;

      pfrface->height = (FT_Short)( ( pfrface->units_per_EM * 12 ) / 10 );
      if ( pfrface->height < pfrface->ascender - pfrface->descender )
        pfrface->height = (FT_Short)(pfrface->ascender - pfrface->descender);

      if ( phy_font->num_strikes > 0 )
      {
        FT_UInt          n, count = phy_font->num_strikes;
        FT_Bitmap_Size*  size;
        PFR_Strike       strike;
        FT_Memory        memory = pfrface->stream->memory;


        if ( FT_QNEW_ARRAY( pfrface->available_sizes, count ) )
          goto Exit;

        size   = pfrface->available_sizes;
        strike = phy_font->strikes;
        for ( n = 0; n < count; n++, size++, strike++ )
        {
          size->height = (FT_Short)strike->y_ppm;
          size->width  = (FT_Short)strike->x_ppm;
          size->size   = (FT_Pos)( strike->y_ppm << 6 );
          size->x_ppem = (FT_Pos)( strike->x_ppm << 6 );
          size->y_ppem = (FT_Pos)( strike->y_ppm << 6 );
        }
        pfrface->num_fixed_sizes = (FT_Int)count;
      }

      /* now compute maximum advance width */
      if ( ( phy_font->flags & PFR_PHY_PROPORTIONAL ) == 0 )
        pfrface->max_advance_width = (FT_Short)phy_font->standard_advance;
      else
      {
        FT_Int    max = 0;
        FT_UInt   count = phy_font->num_chars;
        PFR_Char  gchar = phy_font->chars;


        for ( ; count > 0; count--, gchar++ )
        {
          if ( max < gchar->advance )
            max = gchar->advance;
        }

        pfrface->max_advance_width = (FT_Short)max;
      }

      pfrface->max_advance_height = pfrface->height;

      pfrface->underline_position  = (FT_Short)( -pfrface->units_per_EM / 10 );
      pfrface->underline_thickness = (FT_Short)(  pfrface->units_per_EM / 30 );

      /* create charmap */
      {
        FT_CharMapRec  charmap;


        charmap.face        = pfrface;
        charmap.platform_id = TT_PLATFORM_MICROSOFT;
        charmap.encoding_id = TT_MS_ID_UNICODE_CS;
        charmap.encoding    = FT_ENCODING_UNICODE;

        error = FT_CMap_New( &pfr_cmap_class_rec, NULL, &charmap, NULL );
      }

      /* check whether we have loaded any kerning pairs */
      if ( phy_font->num_kern_pairs )
        pfrface->face_flags |= FT_FACE_FLAG_KERNING;
    }

  Exit:
    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                    SLOT OBJECT METHOD                         *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL_DEF( FT_Error )
  pfr_slot_init( FT_GlyphSlot  pfrslot )        /* PFR_Slot */
  {
    PFR_Slot        slot   = (PFR_Slot)pfrslot;
    FT_GlyphLoader  loader = pfrslot->internal->loader;


    pfr_glyph_init( &slot->glyph, loader );

    return 0;
  }


  FT_LOCAL_DEF( void )
  pfr_slot_done( FT_GlyphSlot  pfrslot )        /* PFR_Slot */
  {
    PFR_Slot  slot = (PFR_Slot)pfrslot;


    pfr_glyph_done( &slot->glyph );
  }


  FT_LOCAL_DEF( FT_Error )
  pfr_slot_load( FT_GlyphSlot  pfrslot,         /* PFR_Slot */
                 FT_Size       pfrsize,         /* PFR_Size */
                 FT_UInt       gindex,
                 FT_Int32      load_flags )
  {
    PFR_Slot     slot    = (PFR_Slot)pfrslot;
    PFR_Size     size    = (PFR_Size)pfrsize;
    FT_Error     error;
    PFR_Face     face    = (PFR_Face)pfrslot->face;
    PFR_Char     gchar;
    FT_Outline*  outline = &pfrslot->outline;
    FT_ULong     gps_offset;


    FT_TRACE1(( "pfr_slot_load: glyph index %d\n", gindex ));

    if ( gindex > 0 )
      gindex--;

    if ( !face || gindex >= face->phy_font.num_chars )
    {
      error = FT_THROW( Invalid_Argument );
      goto Exit;
    }

    /* try to load an embedded bitmap */
    if ( ( load_flags & ( FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP ) ) == 0 )
    {
      error = pfr_slot_load_bitmap(
                slot,
                size,
                gindex,
                ( load_flags & FT_LOAD_BITMAP_METRICS_ONLY ) != 0 );
      if ( !error )
        goto Exit;
    }

    if ( load_flags & FT_LOAD_SBITS_ONLY )
    {
      error = FT_THROW( Invalid_Argument );
      goto Exit;
    }

    gchar               = face->phy_font.chars + gindex;
    pfrslot->format     = FT_GLYPH_FORMAT_OUTLINE;
    outline->n_points   = 0;
    outline->n_contours = 0;
    gps_offset          = face->header.gps_section_offset;

    /* load the glyph outline (FT_LOAD_NO_RECURSE isn't supported) */
    error = pfr_glyph_load( &slot->glyph, face->root.stream,
                            gps_offset, gchar->gps_offset, gchar->gps_size );

    if ( !error )
    {
      FT_BBox            cbox;
      FT_Glyph_Metrics*  metrics = &pfrslot->metrics;
      FT_Pos             advance;
      FT_UInt            em_metrics, em_outline;
      FT_Bool            scaling;


      scaling = FT_BOOL( !( load_flags & FT_LOAD_NO_SCALE ) );

      /* copy outline data */
      *outline = slot->glyph.loader->base.outline;

      outline->flags &= ~FT_OUTLINE_OWNER;
      outline->flags |= FT_OUTLINE_REVERSE_FILL;

      if ( pfrsize->metrics.y_ppem < 24 )
        outline->flags |= FT_OUTLINE_HIGH_PRECISION;

      /* compute the advance vector */
      metrics->horiAdvance = 0;
      metrics->vertAdvance = 0;

      advance    = gchar->advance;
      em_metrics = face->phy_font.metrics_resolution;
      em_outline = face->phy_font.outline_resolution;

      if ( em_metrics != em_outline )
        advance = FT_MulDiv( advance,
                             (FT_Long)em_outline,
                             (FT_Long)em_metrics );

      if ( face->phy_font.flags & PFR_PHY_VERTICAL )
        metrics->vertAdvance = advance;
      else
        metrics->horiAdvance = advance;

      pfrslot->linearHoriAdvance = metrics->horiAdvance;
      pfrslot->linearVertAdvance = metrics->vertAdvance;

      /* make up vertical metrics(?) */
      metrics->vertBearingX = 0;
      metrics->vertBearingY = 0;

#if 0 /* some fonts seem to be broken here! */

      /* Apply the font matrix, if any.                 */
      /* TODO: Test existing fonts with unusual matrix  */
      /* whether we have to adjust Units per EM.        */
      {
        FT_Matrix font_matrix;


        font_matrix.xx = face->log_font.matrix[0] << 8;
        font_matrix.yx = face->log_font.matrix[1] << 8;
        font_matrix.xy = face->log_font.matrix[2] << 8;
        font_matrix.yy = face->log_font.matrix[3] << 8;

        FT_Outline_Transform( outline, &font_matrix );
      }
#endif

      /* scale when needed */
      if ( scaling )
      {
        FT_Int      n;
        FT_Fixed    x_scale = pfrsize->metrics.x_scale;
        FT_Fixed    y_scale = pfrsize->metrics.y_scale;
        FT_Vector*  vec     = outline->points;


        /* scale outline points */
        for ( n = 0; n < outline->n_points; n++, vec++ )
        {
          vec->x = FT_MulFix( vec->x, x_scale );
          vec->y = FT_MulFix( vec->y, y_scale );
        }

        /* scale the advance */
        metrics->horiAdvance = FT_MulFix( metrics->horiAdvance, x_scale );
        metrics->vertAdvance = FT_MulFix( metrics->vertAdvance, y_scale );
      }

      /* compute the rest of the metrics */
      FT_Outline_Get_CBox( outline, &cbox );

      metrics->width        = cbox.xMax - cbox.xMin;
      metrics->height       = cbox.yMax - cbox.yMin;
      metrics->horiBearingX = cbox.xMin;
      metrics->horiBearingY = cbox.yMax - metrics->height;
    }

  Exit:
    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                      KERNING METHOD                           *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL_DEF( FT_Error )
  pfr_face_get_kerning( FT_Face     pfrface,        /* PFR_Face */
                        FT_UInt     glyph1,
                        FT_UInt     glyph2,
                        FT_Vector*  kerning )
  {
    PFR_Face     face     = (PFR_Face)pfrface;
    FT_Error     error    = FT_Err_Ok;
    PFR_PhyFont  phy_font = &face->phy_font;
    FT_UInt32    code1, code2, pair;


    kerning->x = 0;
    kerning->y = 0;

    if ( glyph1 > 0 )
      glyph1--;

    if ( glyph2 > 0 )
      glyph2--;

    /* convert glyph indices to character codes */
    if ( glyph1 > phy_font->num_chars ||
         glyph2 > phy_font->num_chars )
      goto Exit;

    code1 = phy_font->chars[glyph1].char_code;
    code2 = phy_font->chars[glyph2].char_code;
    pair  = PFR_KERN_INDEX( code1, code2 );

    /* now search the list of kerning items */
    {
      PFR_KernItem  item   = phy_font->kern_items;
      FT_Stream     stream = pfrface->stream;


      for ( ; item; item = item->next )
      {
        if ( pair >= item->pair1 && pair <= item->pair2 )
          goto FoundPair;
      }
      goto Exit;

    FoundPair: /* we found an item, now parse it and find the value if any */
      if ( FT_STREAM_SEEK( item->offset )                       ||
           FT_FRAME_ENTER( item->pair_count * item->pair_size ) )
        goto Exit;

      {
        FT_UInt    count       = item->pair_count;
        FT_UInt    size        = item->pair_size;
        FT_UInt    power       = 1 << FT_MSB( count );
        FT_UInt    probe       = power * size;
        FT_UInt    extra       = count - power;
        FT_Byte*   base        = stream->cursor;
        FT_Bool    twobytes    = FT_BOOL( item->flags & PFR_KERN_2BYTE_CHAR );
        FT_Bool    twobyte_adj = FT_BOOL( item->flags & PFR_KERN_2BYTE_ADJ  );
        FT_Byte*   p;
        FT_UInt32  cpair;


        if ( extra > 0 )
        {
          p = base + extra * size;

          if ( twobytes )
            cpair = FT_NEXT_ULONG( p );
          else
            cpair = PFR_NEXT_KPAIR( p );

          if ( cpair == pair )
            goto Found;

          if ( cpair < pair )
          {
            if ( twobyte_adj )
              p += 2;
            else
              p++;
            base = p;
          }
        }

        while ( probe > size )
        {
          probe >>= 1;
          p       = base + probe;

          if ( twobytes )
            cpair = FT_NEXT_ULONG( p );
          else
            cpair = PFR_NEXT_KPAIR( p );

          if ( cpair == pair )
            goto Found;

          if ( cpair < pair )
            base += probe;
        }

        p = base;

        if ( twobytes )
          cpair = FT_NEXT_ULONG( p );
        else
          cpair = PFR_NEXT_KPAIR( p );

        if ( cpair == pair )
        {
          FT_Int  value;


        Found:
          if ( twobyte_adj )
            value = FT_PEEK_SHORT( p );
          else
            value = p[0];

          kerning->x = item->base_adj + value;
        }
      }

      FT_FRAME_EXIT();
    }

  Exit:
    return error;
  }


/* END */
