/****************************************************************************
 *
 * pfrsbit.c
 *
 *   FreeType PFR bitmap loader (body).
 *
 * Copyright (C) 2002-2022 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 "pfrsbit.h"
#include "pfrload.h"
#include <freetype/internal/ftdebug.h>
#include <freetype/internal/ftstream.h>

#include "pfrerror.h"

#undef  FT_COMPONENT
#define FT_COMPONENT  pfr


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                      PFR BIT WRITER                           *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  typedef struct  PFR_BitWriter_
  {
    FT_Byte*  line;      /* current line start               */
    FT_Int    pitch;     /* line size in bytes               */
    FT_UInt   width;     /* width in pixels/bits             */
    FT_UInt   rows;      /* number of remaining rows to scan */
    FT_UInt   total;     /* total number of bits to draw     */

  } PFR_BitWriterRec, *PFR_BitWriter;


  static void
  pfr_bitwriter_init( PFR_BitWriter  writer,
                      FT_Bitmap*     target,
                      FT_Bool        decreasing )
  {
    writer->line   = target->buffer;
    writer->pitch  = target->pitch;
    writer->width  = target->width;
    writer->rows   = target->rows;
    writer->total  = writer->width * writer->rows;

    if ( !decreasing )
    {
      writer->line += writer->pitch * (FT_Int)( target->rows - 1 );
      writer->pitch = -writer->pitch;
    }
  }


  static void
  pfr_bitwriter_decode_bytes( PFR_BitWriter  writer,
                              FT_Byte*       p,
                              FT_Byte*       limit )
  {
    FT_UInt   n, reload;
    FT_UInt   left = writer->width;
    FT_Byte*  cur  = writer->line;
    FT_UInt   mask = 0x80;
    FT_UInt   val  = 0;
    FT_UInt   c    = 0;


    n = (FT_UInt)( limit - p ) * 8;
    if ( n > writer->total )
      n = writer->total;

    reload = n & 7;

    for ( ; n > 0; n-- )
    {
      if ( ( n & 7 ) == reload )
        val = *p++;

      if ( val & 0x80 )
        c |= mask;

      val  <<= 1;
      mask >>= 1;

      if ( --left <= 0 )
      {
        cur[0] = (FT_Byte)c;
        left   = writer->width;
        mask   = 0x80;

        writer->line += writer->pitch;
        cur           = writer->line;
        c             = 0;
      }
      else if ( mask == 0 )
      {
        cur[0] = (FT_Byte)c;
        mask   = 0x80;
        c      = 0;
        cur++;
      }
    }

    if ( mask != 0x80 )
      cur[0] = (FT_Byte)c;
  }


  static void
  pfr_bitwriter_decode_rle1( PFR_BitWriter  writer,
                             FT_Byte*       p,
                             FT_Byte*       limit )
  {
    FT_Int    phase, count, counts[2];
    FT_UInt   n, reload;
    FT_UInt   left = writer->width;
    FT_Byte*  cur  = writer->line;
    FT_UInt   mask = 0x80;
    FT_UInt   c    = 0;


    n = writer->total;

    phase     = 1;
    counts[0] = 0;
    counts[1] = 0;
    count     = 0;
    reload    = 1;

    for ( ; n > 0; n-- )
    {
      if ( reload )
      {
        do
        {
          if ( phase )
          {
            FT_Int  v;


            if ( p >= limit )
              break;

            v         = *p++;
            counts[0] = v >> 4;
            counts[1] = v & 15;
            phase     = 0;
            count     = counts[0];
          }
          else
          {
            phase = 1;
            count = counts[1];
          }

        } while ( count == 0 );
      }

      if ( phase )
        c |= mask;

      mask >>= 1;

      if ( --left <= 0 )
      {
        cur[0] = (FT_Byte)c;
        left   = writer->width;
        mask   = 0x80;

        writer->line += writer->pitch;
        cur           = writer->line;
        c             = 0;
      }
      else if ( mask == 0 )
      {
        cur[0] = (FT_Byte)c;
        mask   = 0x80;
        c      = 0;
        cur++;
      }

      reload = ( --count <= 0 );
    }

    if ( mask != 0x80 )
      cur[0] = (FT_Byte) c;
  }


  static void
  pfr_bitwriter_decode_rle2( PFR_BitWriter  writer,
                             FT_Byte*       p,
                             FT_Byte*       limit )
  {
    FT_Int    phase, count;
    FT_UInt   n, reload;
    FT_UInt   left = writer->width;
    FT_Byte*  cur  = writer->line;
    FT_UInt   mask = 0x80;
    FT_UInt   c    = 0;


    n = writer->total;

    phase  = 1;
    count  = 0;
    reload = 1;

    for ( ; n > 0; n-- )
    {
      if ( reload )
      {
        do
        {
          if ( p >= limit )
            break;

          count = *p++;
          phase = phase ^ 1;

        } while ( count == 0 );
      }

      if ( phase )
        c |= mask;

      mask >>= 1;

      if ( --left <= 0 )
      {
        cur[0] = (FT_Byte)c;
        c      = 0;
        mask   = 0x80;
        left   = writer->width;

        writer->line += writer->pitch;
        cur           = writer->line;
      }
      else if ( mask == 0 )
      {
        cur[0] = (FT_Byte)c;
        c      = 0;
        mask   = 0x80;
        cur++;
      }

      reload = ( --count <= 0 );
    }

    if ( mask != 0x80 )
      cur[0] = (FT_Byte) c;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                  BITMAP DATA DECODING                         *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  static void
  pfr_lookup_bitmap_data( FT_Byte*   base,
                          FT_Byte*   limit,
                          FT_UInt    count,
                          FT_UInt*   flags,
                          FT_UInt    char_code,
                          FT_ULong*  found_offset,
                          FT_ULong*  found_size )
  {
    FT_UInt   min, max, char_len;
    FT_Bool   two = FT_BOOL( *flags & PFR_BITMAP_2BYTE_CHARCODE );
    FT_Byte*  buff;


    char_len = 4;
    if ( two )
      char_len += 1;
    if ( *flags & PFR_BITMAP_2BYTE_SIZE )
      char_len += 1;
    if ( *flags & PFR_BITMAP_3BYTE_OFFSET )
      char_len += 1;

    if ( !( *flags & PFR_BITMAP_CHARCODES_VALIDATED ) )
    {
      FT_Byte*  p;
      FT_Byte*  lim;
      FT_UInt   code;
      FT_Long   prev_code;


      *flags    |= PFR_BITMAP_VALID_CHARCODES;
      prev_code  = -1;
      lim        = base + count * char_len;

      if ( lim > limit )
      {
        FT_TRACE0(( "pfr_lookup_bitmap_data:"
                    " number of bitmap records too large,\n" ));
        FT_TRACE0(( "                       "
                    " thus ignoring all bitmaps in this strike\n" ));
        *flags &= ~PFR_BITMAP_VALID_CHARCODES;
      }
      else
      {
        /* check whether records are sorted by code */
        for ( p = base; p < lim; p += char_len )
        {
          if ( two )
            code = FT_PEEK_USHORT( p );
          else
            code = *p;

          if ( (FT_Long)code <= prev_code )
          {
            FT_TRACE0(( "pfr_lookup_bitmap_data:"
                        " bitmap records are not sorted,\n" ));
            FT_TRACE0(( "                       "
                        " thus ignoring all bitmaps in this strike\n" ));
            *flags &= ~PFR_BITMAP_VALID_CHARCODES;
            break;
          }

          prev_code = code;
        }
      }

      *flags |= PFR_BITMAP_CHARCODES_VALIDATED;
    }

    /* ignore bitmaps in case table is not valid     */
    /* (this might be sanitized, but PFR is dead...) */
    if ( !( *flags & PFR_BITMAP_VALID_CHARCODES ) )
      goto Fail;

    min = 0;
    max = count;

    /* binary search */
    while ( min < max )
    {
      FT_UInt  mid, code;


      mid  = ( min + max ) >> 1;
      buff = base + mid * char_len;

      if ( two )
        code = PFR_NEXT_USHORT( buff );
      else
        code = PFR_NEXT_BYTE( buff );

      if ( char_code < code )
        max = mid;
      else if ( char_code > code )
        min = mid + 1;
      else
        goto Found_It;
    }

  Fail:
    /* Not found */
    *found_size   = 0;
    *found_offset = 0;
    return;

  Found_It:
    if ( *flags & PFR_BITMAP_2BYTE_SIZE )
      *found_size = PFR_NEXT_USHORT( buff );
    else
      *found_size = PFR_NEXT_BYTE( buff );

    if ( *flags & PFR_BITMAP_3BYTE_OFFSET )
      *found_offset = PFR_NEXT_ULONG( buff );
    else
      *found_offset = PFR_NEXT_USHORT( buff );
  }


  /* load bitmap metrics.  `*padvance' must be set to the default value */
  /* before calling this function                                       */
  /*                                                                    */
  static FT_Error
  pfr_load_bitmap_metrics( FT_Byte**  pdata,
                           FT_Byte*   limit,
                           FT_Long    scaled_advance,
                           FT_Long   *axpos,
                           FT_Long   *aypos,
                           FT_UInt   *axsize,
                           FT_UInt   *aysize,
                           FT_Long   *aadvance,
                           FT_UInt   *aformat )
  {
    FT_Error  error = FT_Err_Ok;
    FT_Byte   flags;
    FT_Byte   b;
    FT_Byte*  p = *pdata;
    FT_Long   xpos, ypos, advance;
    FT_UInt   xsize, ysize;


    PFR_CHECK( 1 );
    flags = PFR_NEXT_BYTE( p );

    xpos    = 0;
    ypos    = 0;
    xsize   = 0;
    ysize   = 0;
    advance = 0;

    switch ( flags & 3 )
    {
    case 0:
      PFR_CHECK( 1 );
      b    = PFR_NEXT_BYTE( p );
      xpos = (FT_Char)b >> 4;
      ypos = ( (FT_Char)( b << 4 ) ) >> 4;
      break;

    case 1:
      PFR_CHECK( 2 );
      xpos = PFR_NEXT_INT8( p );
      ypos = PFR_NEXT_INT8( p );
      break;

    case 2:
      PFR_CHECK( 4 );
      xpos = PFR_NEXT_SHORT( p );
      ypos = PFR_NEXT_SHORT( p );
      break;

    case 3:
      PFR_CHECK( 6 );
      xpos = PFR_NEXT_LONG( p );
      ypos = PFR_NEXT_LONG( p );
      break;

    default:
      ;
    }

    flags >>= 2;
    switch ( flags & 3 )
    {
    case 0:
      /* blank image */
      xsize = 0;
      ysize = 0;
      break;

    case 1:
      PFR_CHECK( 1 );
      b     = PFR_NEXT_BYTE( p );
      xsize = ( b >> 4 ) & 0xF;
      ysize = b & 0xF;
      break;

    case 2:
      PFR_CHECK( 2 );
      xsize = PFR_NEXT_BYTE( p );
      ysize = PFR_NEXT_BYTE( p );
      break;

    case 3:
      PFR_CHECK( 4 );
      xsize = PFR_NEXT_USHORT( p );
      ysize = PFR_NEXT_USHORT( p );
      break;

    default:
      ;
    }

    flags >>= 2;
    switch ( flags & 3 )
    {
    case 0:
      advance = scaled_advance;
      break;

    case 1:
      PFR_CHECK( 1 );
      advance = PFR_NEXT_INT8( p ) * 256;
      break;

    case 2:
      PFR_CHECK( 2 );
      advance = PFR_NEXT_SHORT( p );
      break;

    case 3:
      PFR_CHECK( 3 );
      advance = PFR_NEXT_LONG( p );
      break;

    default:
      ;
    }

    *axpos    = xpos;
    *aypos    = ypos;
    *axsize   = xsize;
    *aysize   = ysize;
    *aadvance = advance;
    *aformat  = flags >> 2;
    *pdata    = p;

  Exit:
    return error;

  Too_Short:
    error = FT_THROW( Invalid_Table );
    FT_ERROR(( "pfr_load_bitmap_metrics: invalid glyph data\n" ));
    goto Exit;
  }


  static FT_Error
  pfr_load_bitmap_bits( FT_Byte*    p,
                        FT_Byte*    limit,
                        FT_UInt     format,
                        FT_Bool     decreasing,
                        FT_Bitmap*  target )
  {
    FT_Error          error = FT_Err_Ok;
    PFR_BitWriterRec  writer;


    if ( target->rows > 0 && target->width > 0 )
    {
      pfr_bitwriter_init( &writer, target, decreasing );

      switch ( format )
      {
      case 0: /* packed bits */
        pfr_bitwriter_decode_bytes( &writer, p, limit );
        break;

      case 1: /* RLE1 */
        pfr_bitwriter_decode_rle1( &writer, p, limit );
        break;

      case 2: /* RLE2 */
        pfr_bitwriter_decode_rle2( &writer, p, limit );
        break;

      default:
        ;
      }
    }

    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                     BITMAP LOADING                            *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  FT_LOCAL( FT_Error )
  pfr_slot_load_bitmap( PFR_Slot  glyph,
                        PFR_Size  size,
                        FT_UInt   glyph_index,
                        FT_Bool   metrics_only )
  {
    FT_Error     error;
    PFR_Face     face   = (PFR_Face) glyph->root.face;
    FT_Stream    stream = face->root.stream;
    PFR_PhyFont  phys   = &face->phy_font;
    FT_ULong     gps_offset;
    FT_ULong     gps_size;
    PFR_Char     character;
    PFR_Strike   strike;


    character = &phys->chars[glyph_index];

    /* look up a bitmap strike corresponding to the current */
    /* character dimensions                                 */
    {
      FT_UInt  n;


      strike = phys->strikes;
      for ( n = 0; n < phys->num_strikes; n++ )
      {
        if ( strike->x_ppm == (FT_UInt)size->root.metrics.x_ppem &&
             strike->y_ppm == (FT_UInt)size->root.metrics.y_ppem )
          goto Found_Strike;

        strike++;
      }

      /* couldn't find it */
      return FT_THROW( Invalid_Argument );
    }

  Found_Strike:

    /* now look up the glyph's position within the file */
    {
      FT_UInt  char_len;


      char_len = 4;
      if ( strike->flags & PFR_BITMAP_2BYTE_CHARCODE )
        char_len += 1;
      if ( strike->flags & PFR_BITMAP_2BYTE_SIZE )
        char_len += 1;
      if ( strike->flags & PFR_BITMAP_3BYTE_OFFSET )
        char_len += 1;

      /* access data directly in the frame to speed lookups */
      if ( FT_STREAM_SEEK( phys->bct_offset + strike->bct_offset ) ||
           FT_FRAME_ENTER( char_len * strike->num_bitmaps )        )
        goto Exit;

      pfr_lookup_bitmap_data( stream->cursor,
                              stream->limit,
                              strike->num_bitmaps,
                              &strike->flags,
                              character->char_code,
                              &gps_offset,
                              &gps_size );

      FT_FRAME_EXIT();

      if ( gps_size == 0 )
      {
        /* could not find a bitmap program string for this glyph */
        error = FT_THROW( Invalid_Argument );
        goto Exit;
      }
    }

    /* get the bitmap metrics */
    {
      FT_Long   xpos = 0, ypos = 0, advance = 0;
      FT_UInt   xsize = 0, ysize = 0, format = 0;
      FT_Byte*  p;


      /* compute linear advance */
      advance = character->advance;
      if ( phys->metrics_resolution != phys->outline_resolution )
        advance = FT_MulDiv( advance,
                             (FT_Long)phys->outline_resolution,
                             (FT_Long)phys->metrics_resolution );

      glyph->root.linearHoriAdvance = advance;

      /* compute default advance, i.e., scaled advance; this can be */
      /* overridden in the bitmap header of certain glyphs          */
      advance = FT_MulDiv( (FT_Fixed)size->root.metrics.x_ppem << 8,
                           character->advance,
                           (FT_Long)phys->metrics_resolution );

      if ( FT_STREAM_SEEK( face->header.gps_section_offset + gps_offset ) ||
           FT_FRAME_ENTER( gps_size )                                     )
        goto Exit;

      p     = stream->cursor;
      error = pfr_load_bitmap_metrics( &p, stream->limit,
                                       advance,
                                       &xpos, &ypos,
                                       &xsize, &ysize,
                                       &advance, &format );
      if ( error )
        goto Exit1;

      /*
       * Before allocating the target bitmap, we check whether the given
       * bitmap dimensions are valid, depending on the image format.
       *
       * Format 0: We have a stream of pixels (with 8 pixels per byte).
       *
       *             (xsize * ysize + 7) / 8 <= gps_size
       *
       * Format 1: Run-length encoding; the high nibble holds the number of
       *           white bits, the low nibble the number of black bits.  In
       *           other words, a single byte can represent at most 15
       *           pixels.
       *
       *             xsize * ysize <= 15 * gps_size
       *
       * Format 2: Run-length encoding; the high byte holds the number of
       *           white bits, the low byte the number of black bits.  In
       *           other words, two bytes can represent at most 255 pixels.
       *
       *             xsize * ysize <= 255 * (gps_size + 1) / 2
       */
      switch ( format )
      {
      case 0:
        if ( ( (FT_ULong)xsize * ysize + 7 ) / 8 > gps_size )
          error = FT_THROW( Invalid_Table );
        break;
      case 1:
        if ( (FT_ULong)xsize * ysize > 15 * gps_size )
          error = FT_THROW( Invalid_Table );
        break;
      case 2:
        if ( (FT_ULong)xsize * ysize > 255 * ( ( gps_size + 1 ) / 2 ) )
          error = FT_THROW( Invalid_Table );
        break;
      default:
        FT_ERROR(( "pfr_slot_load_bitmap: invalid image type\n" ));
        error = FT_THROW( Invalid_Table );
      }

      if ( error )
      {
        if ( FT_ERR_EQ( error, Invalid_Table ) )
          FT_ERROR(( "pfr_slot_load_bitmap: invalid bitmap dimensions\n" ));
        goto Exit1;
      }

      /*
       * XXX: on 16bit systems we return an error for huge bitmaps
       *      that cause size truncation, because truncated
       *      size properties make bitmap glyphs broken.
       */
      if ( xpos > FT_INT_MAX                  ||
           xpos < FT_INT_MIN                  ||
           ysize > FT_INT_MAX                 ||
           ypos > FT_INT_MAX - (FT_Long)ysize ||
           ypos + (FT_Long)ysize < FT_INT_MIN )
      {
        FT_TRACE1(( "pfr_slot_load_bitmap:" ));
        FT_TRACE1(( "huge bitmap glyph %ldx%ld over FT_GlyphSlot\n",
                     xpos, ypos ));
        error = FT_THROW( Invalid_Pixel_Size );
      }

      if ( !error )
      {
        glyph->root.format = FT_GLYPH_FORMAT_BITMAP;

        /* Set up glyph bitmap and metrics */

        /* XXX: needs casts to fit FT_Bitmap.{width|rows|pitch} */
        glyph->root.bitmap.width      = xsize;
        glyph->root.bitmap.rows       = ysize;
        glyph->root.bitmap.pitch      = (FT_Int)( xsize + 7 ) >> 3;
        glyph->root.bitmap.pixel_mode = FT_PIXEL_MODE_MONO;

        /* XXX: needs casts to fit FT_Glyph_Metrics.{width|height} */
        glyph->root.metrics.width        = (FT_Pos)xsize << 6;
        glyph->root.metrics.height       = (FT_Pos)ysize << 6;
        glyph->root.metrics.horiBearingX = xpos * 64;
        glyph->root.metrics.horiBearingY = ypos * 64;
        glyph->root.metrics.horiAdvance  = FT_PIX_ROUND( ( advance >> 2 ) );
        glyph->root.metrics.vertBearingX = - glyph->root.metrics.width >> 1;
        glyph->root.metrics.vertBearingY = 0;
        glyph->root.metrics.vertAdvance  = size->root.metrics.height;

        /* XXX: needs casts fit FT_GlyphSlotRec.bitmap_{left|top} */
        glyph->root.bitmap_left = (FT_Int)xpos;
        glyph->root.bitmap_top  = (FT_Int)( ypos + (FT_Long)ysize );

        if ( metrics_only )
          goto Exit1;

        /* Allocate and read bitmap data */
        {
          FT_ULong  len = (FT_ULong)glyph->root.bitmap.pitch * ysize;


          error = ft_glyphslot_alloc_bitmap( &glyph->root, len );
          if ( !error )
            error = pfr_load_bitmap_bits(
                      p,
                      stream->limit,
                      format,
                      FT_BOOL( face->header.color_flags &
                               PFR_FLAG_INVERT_BITMAP   ),
                      &glyph->root.bitmap );
        }
      }

    Exit1:
      FT_FRAME_EXIT();
    }

  Exit:
    return error;
  }


/* END */
