The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "imager.h"
#include "imrender.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>


/*
=head1 NAME

fontft1.c - Freetype 1.x font driver for Imager

=head1 SYNOPSIS

  handle = i_tt_new(path_to_ttf);
  rc = i_tt_bbox(handle, points, "foo", 3, int cords[6], utf8);
  i_tt_destroy(handle);

  // and much more

=head1 DESCRIPTION

fontft1.c implements font creation, rendering, bounding box functions and
more for Imager using Freetype 1.x.

In general this driver should be ignored in favour of the FT2 driver.

=head1 FUNCTION REFERENCE

Some of these functions are internal.

=over

=cut

*/


/* Truetype font support */
/* These are enabled by default when configuring Freetype 1.x
   I haven't a clue how to reliably detect it at compile time.

   We need a compilation probe in Makefile.PL
*/
#define FTXPOST 1
#define FTXERR18 1

#include <freetype.h>
#define TT_CHC 5

#ifdef FTXPOST
#include <ftxpost.h>
#endif

#ifdef FTXERR18
#include <ftxerr18.h>
#endif

/* some versions of FT1.x don't seem to define this - it's font defined
   so it won't change */
#ifndef TT_MS_LANGID_ENGLISH_GENERAL
#define TT_MS_LANGID_ENGLISH_GENERAL 0x0409
#endif

static im_slot_t slot = -1;

/* convert a code point into an index in the glyph cache */
#define TT_HASH(x) ((x) & 0xFF)

typedef struct {
  int initialized;
  TT_Engine engine;
} i_tt_engine;

typedef struct i_glyph_entry_ {
  TT_Glyph glyph;
  unsigned long ch;
} i_tt_glyph_entry;

#define TT_NOCHAR (~0UL)

struct TT_Instancehandle_ {
  TT_Instance instance;
  TT_Instance_Metrics imetrics;
  TT_Glyph_Metrics gmetrics[256];
  i_tt_glyph_entry glyphs[256];
  int smooth;
  int order;
  i_img_dim ptsize;
};

typedef struct TT_Instancehandle_ TT_Instancehandle;

struct TT_Fonthandle_ {
  TT_Face face;
  TT_Face_Properties properties;
  TT_Instancehandle instanceh[TT_CHC];
  TT_CharMap char_map;
#ifdef FTXPOST
  int loaded_names;
  TT_Error load_cond;
#endif
};

/* Defines */

#define USTRCT(x) ((x).z)
#define TT_VALID( handle )  ( ( handle ).z != NULL )

static void i_tt_push_error(TT_Error rc);
static void i_tt_uninit(void *);

/* Prototypes */

static  int i_tt_get_instance( TT_Fonthandle *handle, i_img_dim points, int smooth );
static void i_tt_init_raster_map( TT_Raster_Map* bit, i_img_dim width, i_img_dim height, int smooth );
static void i_tt_done_raster_map( TT_Raster_Map *bit );
static void i_tt_clear_raster_map( TT_Raster_Map* bit );
static void i_tt_blit_or( TT_Raster_Map *dst, TT_Raster_Map *src,i_img_dim x_off, i_img_dim y_off );
static  int i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j );
static void 
i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, 
                   TT_Raster_Map *bit, TT_Raster_Map *small_bit, 
                   i_img_dim x_off, i_img_dim y_off, int smooth );
static int
i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, 
                        TT_Raster_Map *small_bit, i_img_dim cords[6], 
                        char const* txt, size_t len, int smooth, int utf8 );
static void i_tt_dump_raster_map2( i_img* im, TT_Raster_Map* bit, i_img_dim xb, i_img_dim yb, const i_color *cl, int smooth );
static void i_tt_dump_raster_map_channel( i_img* im, TT_Raster_Map* bit, i_img_dim xb, i_img_dim yb, int channel, int smooth );
static  int
i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, i_img_dim cords[6], 
                double points, char const* txt, size_t len, int smooth, int utf8 );
static undef_int i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, size_t len, i_img_dim cords[6], int utf8 );


/* static globals needed */

static int  LTT_dpi    = 72; /* FIXME: this ought to be a part of the call interface */
static int  LTT_hinted = 1;  /* FIXME: this too */


/*
 * FreeType interface
 */

void
i_tt_start(void) {
  if (slot == -1)
    slot = im_context_slot_new(i_tt_uninit);
}


/*
=item init_tt()

Initializes the freetype font rendering engine (if needed)

=cut
*/

static i_tt_engine *
i_init_tt(void) {
  TT_Error  error;
  im_context_t ctx = im_get_context();
  TT_Byte palette[] = { 0, 64, 127, 191, 255 };
  i_tt_engine *result = im_context_slot_get(ctx, slot);

  i_clear_error();

  if (result == NULL) {
    result = mymalloc(sizeof(i_tt_engine));
    memset(result, 0, sizeof(*result));
    im_context_slot_set(ctx, slot, result);
    mm_log((1, "allocated FT1 state %p\n", result));
  }

  mm_log((1,"init_tt()\n"));

  if (result->initialized)
    return result;

  error = TT_Init_FreeType( &result->engine );
  if ( error ){
    mm_log((1,"Initialization of freetype failed, code = 0x%x\n",
	    (unsigned)error));
    i_tt_push_error(error);
    i_push_error(0, "Could not initialize freetype 1.x");
    return NULL;
  }

#ifdef FTXPOST
  error = TT_Init_Post_Extension( result->engine );
  if (error) {
    mm_log((1, "Initialization of Post extension failed = 0x%x\n",
	    (unsigned)error));
    
    i_tt_push_error(error);
    i_push_error(0, "Could not initialize FT 1.x POST extension");
    return NULL;
  }
#endif

  error = TT_Set_Raster_Gray_Palette(result->engine, palette);
  if (error) {
    mm_log((1, "Initialization of gray levels failed = 0x%x\n",
	    (unsigned)error));
    i_tt_push_error(error);
    i_push_error(0, "Could not initialize FT 1.x POST extension");
    return NULL;
  }

  mm_log((1, "initialized FT1 state %p\n", result));

  result->initialized = 1;

  return result;
}

static void
i_tt_uninit(void *p) {
  i_tt_engine *tteng = p;

  if (tteng->initialized) {
    mm_log((1, "finalizing FT1 state %p\n", tteng));
    TT_Done_FreeType(tteng->engine);
  }
  mm_log((1, "freeing FT1 state %p\n", tteng));
  myfree(tteng);
}

/* 
=item i_tt_get_instance(handle, points, smooth)

Finds a points+smooth instance or if one doesn't exist in the cache
allocates room and returns its cache entry

   fontname - path to the font to load
   handle   - handle to the font.
   points   - points of the requested font
   smooth   - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
int
i_tt_get_instance( TT_Fonthandle *handle, i_img_dim points, int smooth ) {
  int i,idx;
  TT_Error error;
  
  mm_log((1,"i_tt_get_instance(handle %p, points %" i_DF ", smooth %d)\n",
          handle, i_DFc(points), smooth));
  
  if (smooth == -1) { /* Smooth doesn't matter for this search */
    for(i=0;i<TT_CHC;i++) {
      if (handle->instanceh[i].ptsize==points) {
        mm_log((1,"i_tt_get_instance: in cache - (non selective smoothing search) returning %d\n",i));
        return i;
      }
    }
    smooth=1; /* We will be adding a font - add it as smooth then */
  } else { /* Smooth doesn't matter for this search */
    for(i=0;i<TT_CHC;i++) {
      if (handle->instanceh[i].ptsize == points 
          && handle->instanceh[i].smooth == smooth) {
        mm_log((1,"i_tt_get_instance: in cache returning %d\n",i));
        return i;
      }
    }
  }
  
  /* Found the instance in the cache - return the cache index */
  
  for(idx=0;idx<TT_CHC;idx++) {
    if (!(handle->instanceh[idx].order)) break; /* find the lru item */
  }

  mm_log((1,"i_tt_get_instance: lru item is %d\n",idx));
  mm_log((1,"i_tt_get_instance: lru pointer %p\n",
          USTRCT(handle->instanceh[idx].instance) ));
  
  if ( USTRCT(handle->instanceh[idx].instance) ) {
    mm_log((1,"i_tt_get_instance: freeing lru item from cache %d\n",idx));

    /* Free cached glyphs */
    for(i=0;i<256;i++)
      if ( USTRCT(handle->instanceh[idx].glyphs[i].glyph) )
	TT_Done_Glyph( handle->instanceh[idx].glyphs[i].glyph );

    for(i=0;i<256;i++) {
      handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
      USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
    }

    /* Free instance if needed */
    TT_Done_Instance( handle->instanceh[idx].instance );
  }
  
  /* create and initialize instance */
  /* FIXME: probably a memory leak on fail */
  
  (void) (( error = TT_New_Instance( handle->face, &handle->instanceh[idx].instance ) ) || 
	  ( error = TT_Set_Instance_Resolutions( handle->instanceh[idx].instance, LTT_dpi, LTT_dpi ) ) ||
	  ( error = TT_Set_Instance_CharSize( handle->instanceh[idx].instance, points*64 ) ) );
  
  if ( error ) {
    mm_log((1, "Could not create and initialize instance: error %x.\n",
	    (unsigned)error ));
    return -1;
  }
  
  /* Now that the instance should the inplace we need to lower all of the
     ru counts and put `this' one with the highest entry */
  
  for(i=0;i<TT_CHC;i++) handle->instanceh[i].order--;

  handle->instanceh[idx].order=TT_CHC-1;
  handle->instanceh[idx].ptsize=points;
  handle->instanceh[idx].smooth=smooth;
  TT_Get_Instance_Metrics( handle->instanceh[idx].instance, &(handle->instanceh[idx].imetrics) );

  /* Zero the memory for the glyph storage so they are not thought as
     cached if they haven't been cached since this new font was loaded */

  for(i=0;i<256;i++) {
    handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
    USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
  }
  
  return idx;
}


/*
=item i_tt_new(fontname)

Creates a new font handle object, finds a character map and initialise the
the font handle's cache

   fontname - path to the font to load

=cut
*/

TT_Fonthandle*
i_tt_new(const char *fontname) {
  TT_Error error;
  TT_Fonthandle *handle;
  unsigned short i,n;
  unsigned short platform,encoding;
  i_tt_engine *tteng;

  if ((tteng = i_init_tt()) == NULL) {
    i_push_error(0, "Could not initialize FT1 engine");
    return NULL;
  }

  i_clear_error();
  
  mm_log((1,"i_tt_new(fontname '%s')\n",fontname));
  
  /* allocate memory for the structure */
  
  handle = mymalloc( sizeof(TT_Fonthandle) ); /* checked 5Nov05 tonyc */

  /* load the typeface */
  error = TT_Open_Face( tteng->engine, fontname, &handle->face );
  if ( error ) {
    myfree(handle);
    if ( error == TT_Err_Could_Not_Open_File ) {
      mm_log((1, "Could not find/open %s.\n", fontname ));
    }
    else {
      mm_log((1, "Error while opening %s, error code = 0x%x.\n",fontname, 
              (unsigned)error )); 
    }
    i_tt_push_error(error);
    return NULL;
  }
  
  TT_Get_Face_Properties( handle->face, &(handle->properties) );

  /* First, look for a Unicode charmap */
  n = handle->properties.num_CharMaps;
  USTRCT( handle->char_map )=NULL; /* Invalidate character map */
  
  for ( i = 0; i < n; i++ ) {
    TT_Get_CharMap_ID( handle->face, i, &platform, &encoding );
    if ( (platform == 3 && encoding == 1 ) 
         || (platform == 0 && encoding == 0 ) ) {
      mm_log((2,"i_tt_new - found char map platform %u encoding %u\n", 
              platform, encoding));
      TT_Get_CharMap( handle->face, i, &(handle->char_map) );
      break;
    }
  }
  if (!USTRCT(handle->char_map) && n != 0) {
    /* just use the first one */
    TT_Get_CharMap( handle->face, 0, &(handle->char_map));
  }

  /* Zero the pointsizes - and ordering */
  
  for(i=0;i<TT_CHC;i++) {
    USTRCT(handle->instanceh[i].instance)=NULL;
    handle->instanceh[i].order=i;
    handle->instanceh[i].ptsize=0;
    handle->instanceh[i].smooth=-1;
  }

#ifdef FTXPOST
  handle->loaded_names = 0;
#endif

  mm_log((1,"i_tt_new <- %p\n",handle));
  return handle;
}



/*
 * raster map management
 */

/* 
=item i_tt_init_raster_map(bit, width, height, smooth)

Allocates internal memory for the bitmap as needed by the parameters (internal)
		 
   bit    - bitmap to allocate into
   width  - width of the bitmap
   height - height of the bitmap
   smooth - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
void
i_tt_init_raster_map( TT_Raster_Map* bit, i_img_dim width, i_img_dim height, int smooth ) {

  mm_log((1,"i_tt_init_raster_map( bit %p, width %" i_DF ", height %" i_DF
	  ", smooth %d)\n", bit, i_DFc(width), i_DFc(height), smooth));
  
  bit->rows  = height;
  bit->width = ( width + 3 ) & -4;
  bit->flow  = TT_Flow_Down;
  
  if ( smooth ) {
    bit->cols  = bit->width;
    bit->size  = bit->rows * bit->width;
  } else {
    bit->cols  = ( bit->width + 7 ) / 8;    /* convert to # of bytes     */
    bit->size  = bit->rows * bit->cols;     /* number of bytes in buffer */
  }

  /* rows can be 0 for some glyphs, for example ' ' */
  if (bit->rows && bit->size / bit->rows != bit->cols) {
    i_fatal(0, "Integer overflow calculating bitmap size (%d, %d)\n",
            bit->width, bit->rows);
  }
  
  mm_log((1,"i_tt_init_raster_map: bit->width %d, bit->cols %d, bit->rows %d, bit->size %ld)\n", bit->width, bit->cols, bit->rows, bit->size ));

  bit->bitmap = (void *) mymalloc( bit->size ); /* checked 6Nov05 tonyc */
  if ( !bit->bitmap ) i_fatal(0,"Not enough memory to allocate bitmap (%d)!\n",bit->size );
}


/*
=item i_tt_clear_raster_map(bit)

Frees the bitmap data and sets pointer to NULL (internal)
		 
   bit - bitmap to free

=cut
*/

static
void
i_tt_done_raster_map( TT_Raster_Map *bit ) {
  myfree( bit->bitmap );
  bit->bitmap = NULL;
}


/*
=item i_tt_clear_raster_map(bit)

Clears the specified bitmap (internal)
		 
   bit - bitmap to zero

=cut
*/


static
void
i_tt_clear_raster_map( TT_Raster_Map*  bit ) {
  memset( bit->bitmap, 0, bit->size );
}


/* 
=item i_tt_blit_or(dst, src, x_off, y_off)

function that blits one raster map into another (internal)
		 
   dst   - destination bitmap
   src   - source bitmap
   x_off - x offset into the destination bitmap
   y_off - y offset into the destination bitmap

=cut
*/

static
void
i_tt_blit_or( TT_Raster_Map *dst, TT_Raster_Map *src,i_img_dim x_off, i_img_dim y_off ) {
  i_img_dim  x,  y;
  i_img_dim  x1, x2, y1, y2;
  unsigned char *s, *d;
  
  x1 = x_off < 0 ? -x_off : 0;
  y1 = y_off < 0 ? -y_off : 0;
  
  x2 = (int)dst->cols - x_off;
  if ( x2 > src->cols ) x2 = src->cols;
  
  y2 = (int)dst->rows - y_off;
  if ( y2 > src->rows ) y2 = src->rows;

  if ( x1 >= x2 ) return;

  /* do the real work now */

  for ( y = y1; y < y2; ++y ) {
    s = ( (unsigned char*)src->bitmap ) + y * src->cols + x1;
    d = ( (unsigned char*)dst->bitmap ) + ( y + y_off ) * dst->cols + x1 + x_off;
    
    for ( x = x1; x < x2; ++x ) {
      if (*s > *d)
	*d = *s;
      d++;
      s++;
    }
  }
}

/* useful for debugging */
#if 0

static void dump_raster_map(FILE *out, TT_Raster_Map *bit ) {
  int x, y;
  fprintf(out, "cols %d rows %d  flow %d\n", bit->cols, bit->rows, bit->flow);
  for (y = 0; y < bit->rows; ++y) {
    fprintf(out, "%2d:", y);
    for (x = 0; x < bit->cols; ++x) {
      if ((x & 7) == 0 && x) putc(' ', out);
      fprintf(out, "%02x", ((unsigned char *)bit->bitmap)[y*bit->cols+x]);
    }
    putc('\n', out);
  }
}

#endif

/* 
=item i_tt_get_glyph(handle, inst, j) 

Function to see if a glyph exists and if so cache it (internal)
		 
   handle - pointer to font handle
   inst   - font instance
   j      - charcode of glyph

=cut
*/

static
int
i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j) {
  unsigned short load_flags, code;
  TT_Error error;

  mm_log((1, "i_tt_get_glyph(handle %p, inst %d, j %lu (%c))\n",
          handle,inst,j, (int)((j >= ' ' && j <= '~') ? j : '.')));
  
  /*mm_log((1, "handle->instanceh[inst].glyphs[j]=0x%08X\n",handle->instanceh[inst].glyphs[j] ));*/

  if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)
       && handle->instanceh[inst].glyphs[TT_HASH(j)].ch == j) {
    mm_log((1,"i_tt_get_glyph: %lu in cache\n",j));
    return 1;
  }

  if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph) ) {
    /* clean up the entry */
    TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
    USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
    handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
  }
  
  /* Ok - it wasn't cached - try to get it in */
  load_flags = TTLOAD_SCALE_GLYPH;
  if ( LTT_hinted ) load_flags |= TTLOAD_HINT_GLYPH;
  
  if ( !TT_VALID(handle->char_map) ) {
    code = (j - ' ' + 1) < 0 ? 0 : (j - ' ' + 1);
    if ( code >= handle->properties.num_Glyphs ) code = 0;
  } else code = TT_Char_Index( handle->char_map, j );
  
  if ( (error = TT_New_Glyph( handle->face, &handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)) ) {
    mm_log((1, "Cannot allocate and load glyph: error %#x.\n", (unsigned)error ));
    i_push_error(error, "TT_New_Glyph()");
    return 0;
  }
  if ( (error = TT_Load_Glyph( handle->instanceh[inst].instance, handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, code, load_flags)) ) {
    mm_log((1, "Cannot allocate and load glyph: error %#x.\n", (unsigned)error ));
    /* Don't leak */
    TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
    USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
    i_push_error(error, "TT_Load_Glyph()");
    return 0;
  }

  /* At this point the glyph should be allocated and loaded */
  handle->instanceh[inst].glyphs[TT_HASH(j)].ch = j;

  /* Next get the glyph metrics */
  error = TT_Get_Glyph_Metrics( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
                                &handle->instanceh[inst].gmetrics[TT_HASH(j)] );
  if (error) {
    mm_log((1, "TT_Get_Glyph_Metrics: error %#x.\n", (unsigned)error ));
    TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
    USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
    handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
    i_push_error(error, "TT_Get_Glyph_Metrics()");
    return 0;
  }

  return 1;
}

/*
=item i_tt_has_chars(handle, text, len, utf8, out)

Check if the given characters are defined by the font.  Note that len
is the number of bytes, not the number of characters (when utf8 is
non-zero).

Returns the number of characters that were checked.

=cut
*/

size_t
i_tt_has_chars(TT_Fonthandle *handle, char const *text, size_t len, int utf8,
               char *out) {
  size_t count = 0;
  mm_log((1, "i_tt_has_chars(handle %p, text %p, len %ld, utf8 %d)\n", 
          handle, text, (long)len, utf8));

  while (len) {
    unsigned long c;
    int index;
    if (utf8) {
      c = i_utf8_advance(&text, &len);
      if (c == ~0UL) {
        i_push_error(0, "invalid UTF8 character");
        return 0;
      }
    }
    else {
      c = (unsigned char)*text++;
      --len;
    }
    
    if (TT_VALID(handle->char_map)) {
      index = TT_Char_Index(handle->char_map, c);
    }
    else {
      index = (c - ' ' + 1) < 0 ? 0 : (c - ' ' + 1);
      if (index >= handle->properties.num_Glyphs)
        index = 0;
    }
    *out++ = index != 0;
    ++count;
  }

  return count;
}

/* 
=item i_tt_destroy(handle)

Clears the data taken by a font including all cached data such as
pixmaps and glyphs
		 
   handle - pointer to font handle

=cut
*/

void
i_tt_destroy( TT_Fonthandle *handle) {
  TT_Close_Face( handle->face );
  myfree( handle );
  
  /* FIXME: Should these be freed automatically by the library? 

  TT_Done_Instance( instance );
  void
    i_tt_done_glyphs( void ) {
    int  i;

    if ( !glyphs ) return;
    
    for ( i = 0; i < 256; ++i ) TT_Done_Glyph( glyphs[i] );
    free( glyphs );
    
    glyphs = NULL;
  }
  */
}


/*
 * FreeType Rendering functions
 */


/* 
=item i_tt_render_glyph(handle, gmetrics, bit, smallbit, x_off, y_off, smooth)

Renders a single glyph into the bit rastermap (internal)

   handle   - pointer to font handle
   gmetrics - the metrics for the glyph to be rendered
   bit      - large bitmap that is the destination for the text
   smallbit - small bitmap that is used only if smooth is true
   x_off    - x offset of glyph
   y_off    - y offset of glyph
   smooth   - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
void
i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, TT_Raster_Map *bit, TT_Raster_Map *small_bit, i_img_dim x_off, i_img_dim y_off, int smooth ) {
  
  mm_log((1,"i_tt_render_glyph(glyph %p, gmetrics %p, bit %p, small_bit %p, x_off %" i_DF ", y_off %" i_DF ", smooth %d)\n",
	  USTRCT(glyph), gmetrics, bit, small_bit, i_DFc(x_off),
	  i_DFc(y_off), smooth));
  
  if ( !smooth ) TT_Get_Glyph_Bitmap( glyph, bit, x_off * 64, y_off * 64);
  else {
    TT_F26Dot6 xmin, ymin;

    xmin =  gmetrics->bbox.xMin & -64;
    ymin =  gmetrics->bbox.yMin & -64;
    
    i_tt_clear_raster_map( small_bit );
    TT_Get_Glyph_Pixmap( glyph, small_bit, -xmin, -ymin );
    i_tt_blit_or( bit, small_bit, xmin/64 + x_off, -ymin/64 - y_off );
  }
}


/*
=item i_tt_render_all_glyphs(handle, inst, bit, small_bit, cords, txt, len, smooth)

calls i_tt_render_glyph to render each glyph into the bit rastermap (internal)

   handle   - pointer to font handle
   inst     - font instance
   bit      - large bitmap that is the destination for the text
   smallbit - small bitmap that is used only if smooth is true
   txt      - string to render
   len      - length of the string to render
   smooth   - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
int
i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit,
                        TT_Raster_Map *small_bit, i_img_dim cords[6], 
                        char const* txt, size_t len, int smooth, int utf8 ) {
  unsigned long j;
  TT_F26Dot6 x,y;
  
  mm_log((1,"i_tt_render_all_glyphs( handle %p, inst %d, bit %p, small_bit %p, txt '%.*s', len %ld, smooth %d, utf8 %d)\n",
	  handle, inst, bit, small_bit, (int)len, txt, (long)len, smooth, utf8));
  
  /* 
     y=-( handle->properties.horizontal->Descender * handle->instanceh[inst].imetrics.y_ppem )/(handle->properties.header->Units_Per_EM);
  */

  x=-cords[0]; /* FIXME: If you font is antialiased this should be expanded by one to allow for aa expansion and the allocation too - do before passing here */
  y=-cords[4];
  
  while (len) {
    if (utf8) {
      j = i_utf8_advance(&txt, &len);
      if (j == ~0UL) {
        i_push_error(0, "invalid UTF8 character");
        return 0;
      }
    }
    else {
      j = (unsigned char)*txt++;
      --len;
    }
    if ( !i_tt_get_glyph(handle,inst,j) ) 
      continue;
    i_tt_render_glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
                       &handle->instanceh[inst].gmetrics[TT_HASH(j)], bit, 
                       small_bit, x, y, smooth );
    x += handle->instanceh[inst].gmetrics[TT_HASH(j)].advance / 64;
  }

  return 1;
}


/*
 * Functions to render rasters (single channel images) onto images
 */

/* 
=item i_tt_dump_raster_map2(im, bit, xb, yb, cl, smooth)

Function to dump a raster onto an image in color used by i_tt_text() (internal).

   im     - image to dump raster on
   bit    - bitmap that contains the text to be dumped to im
   xb, yb - coordinates, left edge and baseline
   cl     - color to use for text
   smooth - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
void
i_tt_dump_raster_map2( i_img* im, TT_Raster_Map* bit, i_img_dim xb, i_img_dim yb, const i_color *cl, int smooth ) {
  unsigned char *bmap;
  i_img_dim x, y;
  mm_log((1,"i_tt_dump_raster_map2(im %p, bit %p, xb %" i_DF ", yb %" i_DF ", cl %p)\n",
	  im, bit, i_DFc(xb), i_DFc(yb), cl));
  
  bmap = bit->bitmap;

  if ( smooth ) {

    i_render r;
    i_render_init(&r, im, bit->cols);
    for(y=0;y<bit->rows;y++) {
      i_render_color(&r, xb, yb+y, bit->cols, bmap + y*bit->cols, cl);
    }
    i_render_done(&r);
  } else {
    unsigned char *bmp = mymalloc(bit->width);
    i_render r;

    i_render_init(&r, im, bit->width);

    for(y=0;y<bit->rows;y++) {
      unsigned mask = 0x80;
      unsigned char *p = bmap + y * bit->cols;
      unsigned char *pout = bmp;

      for(x = 0; x < bit->width; x++) {
	*pout++ = (*p & mask) ? 0xFF : 0;
	mask >>= 1;
	if (!mask) {
	  mask = 0x80;
	  ++p;
	}
      }

      i_render_color(&r, xb, yb+y, bit->width, bmp, cl);
    }

    i_render_done(&r);
    myfree(bmp);
  }
}


/*
=item i_tt_dump_raster_map_channel(im, bit, xb, yb, channel, smooth)

Function to dump a raster onto a single channel image in color (internal)

   im      - image to dump raster on
   bit     - bitmap that contains the text to be dumped to im
   xb, yb  - coordinates, left edge and baseline
   channel - channel to copy to
   smooth  - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
void
i_tt_dump_raster_map_channel( i_img* im, TT_Raster_Map*  bit, i_img_dim xb, i_img_dim yb, int channel, int smooth ) {
  unsigned char *bmap;
  i_color val;
  int c;
  i_img_dim x,y;
  int old_mask = im->ch_mask;
  im->ch_mask = 1 << channel;

  mm_log((1,"i_tt_dump_raster_channel(im %p, bit %p, xb %" i_DF ", yb %" i_DF ", channel %d)\n",
	  im, bit, i_DFc(xb), i_DFc(yb), channel));
  
  bmap = bit->bitmap;
  
  if ( smooth ) {
    for(y=0;y<bit->rows;y++) for(x=0;x<bit->width;x++) {
      c = bmap[y*(bit->cols)+x];
      val.channel[channel] = c;
      i_ppix(im,x+xb,y+yb,&val);
    }
  } else {
    for(y=0;y<bit->rows;y++) {
      unsigned mask = 0x80;
      unsigned char *p = bmap + y * bit->cols;

      for(x=0;x<bit->width;x++) {
	val.channel[channel] = (*p & mask) ? 255 : 0;
	i_ppix(im,x+xb,y+yb,&val);
	
	mask >>= 1;
	if (!mask) {
	  ++p;
	  mask = 0x80;
	}
      }
    }
  }
  im->ch_mask = old_mask;
}


/* 
=item i_tt_rasterize(handle, bit, cords, points, txt, len, smooth) 

interface for generating single channel raster of text (internal)

   handle - pointer to font handle
   bit    - the bitmap that is allocated, rendered into and NOT freed
   cords  - the bounding box (modified in place)
   points - font size to use
   txt    - string to render
   len    - length of the string to render
   smooth - boolean (True: antialias on, False: antialias is off)

=cut
*/

static
int
i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, i_img_dim cords[6], double points, char const* txt, size_t len, int smooth, int utf8 ) {
  int inst;
  i_img_dim width, height;
  TT_Raster_Map small_bit;
  
  /* find or install an instance */
  if ( (inst=i_tt_get_instance(handle,points,smooth)) < 0) { 
    mm_log((1,"i_tt_rasterize: get instance failed\n"));
    return 0;
  }
  
  /* calculate bounding box */
  if (!i_tt_bbox_inst( handle, inst, txt, len, cords, utf8 ))
    return 0;
    
  
  width  = cords[2]-cords[0];
  height = cords[5]-cords[4];
  
  mm_log((1,"i_tt_rasterize: width=%" i_DF ", height=%" i_DF "\n",
	  i_DFc(width), i_DFc(height) )); 
  
  i_tt_init_raster_map ( bit, width, height, smooth );
  i_tt_clear_raster_map( bit );
  if ( smooth ) i_tt_init_raster_map( &small_bit, handle->instanceh[inst].imetrics.x_ppem + 32, height, smooth );
  
  if (!i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len, 
                               smooth, utf8 )) {
    if ( smooth ) 
      i_tt_done_raster_map( &small_bit );
    return 0;
  }

  if ( smooth ) i_tt_done_raster_map( &small_bit );
  return 1;
}



/* 
 * Exported text rendering interfaces
 */


/*
=item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth, utf8)

Interface to text rendering into a single channel in an image

   handle  - pointer to font handle
   im      - image to render text on to
   xb, yb  - coordinates, left edge and baseline
   channel - channel to render into
   points  - font size to use
   txt     - string to render
   len     - length of the string to render
   smooth  - boolean (True: antialias on, False: antialias is off)

=cut
*/

undef_int
i_tt_cp( TT_Fonthandle *handle, i_img *im, i_img_dim xb, i_img_dim yb, int channel, double points, char const* txt, size_t len, int smooth, int utf8, int align ) {

  i_img_dim cords[BOUNDING_BOX_COUNT];
  i_img_dim ascent, st_offset, y;
  TT_Raster_Map bit;
  
  i_clear_error();
  if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
  
  ascent=cords[BBOX_ASCENT];
  st_offset=cords[BBOX_NEG_WIDTH];
  y = align ? yb-ascent : yb;

  i_tt_dump_raster_map_channel( im, &bit, xb-st_offset , y, channel, smooth );
  i_tt_done_raster_map( &bit );

  return 1;
}


/* 
=item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth, utf8) 

Interface to text rendering in a single color onto an image

   handle  - pointer to font handle
   im      - image to render text on to
   xb, yb  - coordinates, left edge and baseline
   cl      - color to use for text
   points  - font size to use
   txt     - string to render
   len     - length of the string to render
   smooth  - boolean (True: antialias on, False: antialias is off)

=cut
*/

undef_int
i_tt_text( TT_Fonthandle *handle, i_img *im, i_img_dim xb, i_img_dim yb, const i_color *cl, double points, char const* txt, size_t len, int smooth, int utf8, int align) {
  i_img_dim cords[BOUNDING_BOX_COUNT];
  i_img_dim ascent, st_offset, y;
  TT_Raster_Map bit;

  i_clear_error();
  
  if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
  
  ascent=cords[BBOX_ASCENT];
  st_offset=cords[BBOX_NEG_WIDTH];
  y = align ? yb-ascent : yb;

  i_tt_dump_raster_map2( im, &bit, xb+st_offset, y, cl, smooth ); 
  i_tt_done_raster_map( &bit );

  return 1;
}


/*
=item i_tt_bbox_inst(handle, inst, txt, len, cords, utf8) 

Function to get texts bounding boxes given the instance of the font (internal)

   handle - pointer to font handle
   inst   -  font instance
   txt    -  string to measure
   len    -  length of the string to render
   cords  - the bounding box (modified in place)

=cut
*/

static
undef_int
i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, size_t len, i_img_dim cords[BOUNDING_BOX_COUNT], int utf8 ) {
  int upm, casc, cdesc, first;
  
  int start    = 0;
  i_img_dim width    = 0;
  int gdescent = 0;
  int gascent  = 0;
  int descent  = 0;
  int ascent   = 0;
  int rightb   = 0;

  unsigned long j;

  mm_log((1,"i_tt_box_inst(handle %p,inst %d,txt '%.*s', len %ld, utf8 %d)\n",
	  handle, inst, (int)len, txt, (long)len, utf8));

  upm     = handle->properties.header->Units_Per_EM;
  gascent  = ( handle->properties.horizontal->Ascender  * handle->instanceh[inst].imetrics.y_ppem + upm - 1) / upm;
  gdescent = ( handle->properties.horizontal->Descender * handle->instanceh[inst].imetrics.y_ppem - upm + 1) / upm;
  
  width   = 0;
  start   = 0;
  
  mm_log((1, "i_tt_box_inst: gascent=%d gdescent=%d\n", gascent, gdescent));

  first=1;
  while (len) {
    if (utf8) {
      j = i_utf8_advance(&txt, &len);
      if (j == ~0UL) {
        i_push_error(0, "invalid UTF8 character");
        return 0;
      }
    }
    else {
      j = (unsigned char)*txt++;
      --len;
    }
    if ( i_tt_get_glyph(handle,inst,j) ) {
      TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + TT_HASH(j);
      width += gm->advance   / 64;
      casc   = (gm->bbox.yMax+63) / 64;
      cdesc  = (gm->bbox.yMin-63) / 64;

      mm_log((1, "i_tt_box_inst: glyph='%c' casc=%d cdesc=%d\n", 
              (int)((j >= ' ' && j <= '~') ? j : '.'), casc, cdesc));

      if (first) {
	start    = gm->bbox.xMin / 64;
	ascent   = (gm->bbox.yMax+63) / 64;
	descent  = (gm->bbox.yMin-63) / 64;
	first = 0;
      }
      if (!len) { /* if at end of string */
	/* the right-side bearing - in case the right-side of a 
	   character goes past the right of the advance width,
	   as is common for italic fonts
	*/
	rightb = gm->advance - gm->bearingX 
	  - (gm->bbox.xMax - gm->bbox.xMin);
	/* fprintf(stderr, "font info last: %d %d %d %d\n", 
	   gm->bbox.xMax, gm->bbox.xMin, gm->advance, rightb); */
      }

      ascent  = (ascent  >  casc ?  ascent : casc );
      descent = (descent < cdesc ? descent : cdesc);
    }
  }
  
  cords[BBOX_NEG_WIDTH]=start;
  cords[BBOX_GLOBAL_DESCENT]=gdescent;
  cords[BBOX_POS_WIDTH]=width;
  if (rightb < 0)
    cords[BBOX_POS_WIDTH] -= rightb / 64;
  cords[BBOX_GLOBAL_ASCENT]=gascent;
  cords[BBOX_DESCENT]=descent;
  cords[BBOX_ASCENT]=ascent;
  cords[BBOX_ADVANCE_WIDTH] = width;
  cords[BBOX_RIGHT_BEARING] = rightb / 64;

  return BBOX_RIGHT_BEARING + 1;
}


/*
=item i_tt_bbox(handle, points, txt, len, cords, utf8)

Interface to get a strings bounding box

   handle - pointer to font handle
   points - font size to use
   txt    - string to render
   len    - length of the string to render
   cords  - the bounding box (modified in place)

=cut
*/

undef_int
i_tt_bbox( TT_Fonthandle *handle, double points,const char *txt,size_t len,i_img_dim cords[6], int utf8) {
  int inst;

  i_clear_error();
  mm_log((1,"i_tt_box(handle %p,points %f,txt '%.*s', len %ld, utf8 %d)\n",
	  handle, points, (int)len, txt, (long)len, utf8));

  if ( (inst=i_tt_get_instance(handle,points,-1)) < 0) {
    i_push_errorf(0, "i_tt_get_instance(%g)", points);
    mm_log((1,"i_tt_text: get instance failed\n"));
    return 0;
  }

  return i_tt_bbox_inst(handle, inst, txt, len, cords, utf8);
}

/*
=item i_tt_face_name(handle, name_buf, name_buf_size)

Retrieve's the font's postscript name.

This is complicated by the need to handle encodings and so on.

=cut
 */
size_t
i_tt_face_name(TT_Fonthandle *handle, char *name_buf, size_t name_buf_size) {
  TT_Face_Properties props;
  int name_count;
  int i;
  TT_UShort platform_id, encoding_id, lang_id, name_id;
  TT_UShort name_len;
  TT_String *name;
  int want_index = -1; /* an acceptable but not perfect name */
  int score = 0;

  i_clear_error();
  
  TT_Get_Face_Properties(handle->face, &props);
  name_count = props.num_Names;
  for (i = 0; i < name_count; ++i) {
    TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
                   &name_id);

    TT_Get_Name_String(handle->face, i, &name, &name_len);

    if (platform_id != TT_PLATFORM_APPLE_UNICODE && name_len
        && name_id == TT_NAME_ID_PS_NAME) {
      int might_want_index = -1;
      int might_score = 0;
      if ((platform_id == TT_PLATFORM_MACINTOSH && encoding_id == TT_MAC_ID_ROMAN)
          ||
          (platform_id == TT_PLATFORM_MICROSOFT && encoding_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)) {
        /* exactly what we want */
        want_index = i;
        break;
      }
      
      if (platform_id == TT_PLATFORM_MICROSOFT
          && (encoding_id & 0xFF) == TT_MS_LANGID_ENGLISH_GENERAL) {
        /* any english is good */
        might_want_index = i;
        might_score = 9;
      }
      /* there might be something in between */
      else {
        /* anything non-unicode is better than nothing */
        might_want_index = i;
        might_score = 1;
      }
      if (might_score > score) {
        score = might_score;
        want_index = might_want_index;
      }
    }
  }

  if (want_index != -1) {
    TT_Get_Name_String(handle->face, want_index, &name, &name_len);
    
    strncpy(name_buf, name, name_buf_size);
    name_buf[name_buf_size-1] = '\0';

    return strlen(name) + 1;
  }
  else {
    i_push_error(0, "no face name present");
    return 0;
  }
}

void i_tt_dump_names(TT_Fonthandle *handle) {
  TT_Face_Properties props;
  int name_count;
  int i;
  TT_UShort platform_id, encoding_id, lang_id, name_id;
  TT_UShort name_len;
  TT_String *name;
  
  TT_Get_Face_Properties(handle->face, &props);
  name_count = props.num_Names;
  for (i = 0; i < name_count; ++i) {
    TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
                   &name_id);
    TT_Get_Name_String(handle->face, i, &name, &name_len);

    printf("# %d: plat %d enc %d lang %d name %d value ", i, platform_id,
           encoding_id, lang_id, name_id);
    if (platform_id == TT_PLATFORM_APPLE_UNICODE) {
      printf("(unicode)\n");
    }
    else {
      printf("'%s'\n", name);
    }
  }
  fflush(stdout);
}

size_t
i_tt_glyph_name(TT_Fonthandle *handle, unsigned long ch, char *name_buf, 
                 size_t name_buf_size) {
#ifdef FTXPOST
  TT_Error rc;
  TT_String *psname;
  TT_UShort index;

  i_clear_error();

  if (!handle->loaded_names) {
    TT_Post post;
    mm_log((1, "Loading PS Names"));
    handle->load_cond = TT_Load_PS_Names(handle->face, &post);
    ++handle->loaded_names;
  }

  if (handle->load_cond) {
    i_push_errorf(handle->load_cond, "error loading names (%#x)",
		  (unsigned)handle->load_cond);
    return 0;
  }
  
  index = TT_Char_Index(handle->char_map, ch);
  if (!index) {
    i_push_error(0, "no such character");
    return 0;
  }

  rc = TT_Get_PS_Name(handle->face, index, &psname);

  if (rc) {
    i_push_error(rc, "error getting name");
    return 0;
  }

  strncpy(name_buf, psname, name_buf_size);
  name_buf[name_buf_size-1] = '\0';

  return strlen(psname) + 1;
#else
  mm_log((1, "FTXPOST extension not enabled\n"));
  i_clear_error();
  i_push_error(0, "Use of FTXPOST extension disabled");

  return 0;
#endif
}

/*
=item i_tt_push_error(code)

Push an error message and code onto the Imager error stack.

=cut
*/
static void
i_tt_push_error(TT_Error rc) {
#ifdef FTXERR18
  TT_String const *msg = TT_ErrToString18(rc);

  i_push_error(rc, msg);
#else
  i_push_errorf(rc, "Error code 0x%04x", (unsigned)rc);
#endif
}


/*
=back

=head1 AUTHOR

Arnar M. Hrafnkelsson <addi@umich.edu>

=head1 SEE ALSO

Imager(3)

=cut
*/