]> granicus.if.org Git - php/commitdiff
updated GD from php3 repository, using it as a test extension for the new
authorStig Bakken <ssb@php.net>
Fri, 16 Apr 1999 12:15:38 +0000 (12:15 +0000)
committerStig Bakken <ssb@php.net>
Fri, 16 Apr 1999 12:15:38 +0000 (12:15 +0000)
directory structure

ext/gd/gd.c [new file with mode: 0644]
ext/gd/gdcache.c [new file with mode: 0644]
ext/gd/gdcache.h [new file with mode: 0644]
ext/gd/gdttf.c [new file with mode: 0644]
ext/gd/gdttf.h [new file with mode: 0644]

diff --git a/ext/gd/gd.c b/ext/gd/gd.c
new file mode 100644 (file)
index 0000000..5edcc9a
--- /dev/null
@@ -0,0 +1,1786 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP HTML Embedded Scripting Language Version 3.0                     |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-1999 PHP Development Team (See Credits file)      |
+   +----------------------------------------------------------------------+
+   | This program is free software; you can redistribute it and/or modify |
+   | it under the terms of one of the following licenses:                 |
+   |                                                                      |
+   |  A) the GNU General Public License as published by the Free Software |
+   |     Foundation; either version 2 of the License, or (at your option) |
+   |     any later version.                                               |
+   |                                                                      |
+   |  B) the PHP License as published by the PHP Development Team and     |
+   |     included in the distribution in the file: LICENSE                |
+   |                                                                      |
+   | This program is distributed in the hope that it will be useful,      |
+   | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
+   | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        |
+   | GNU General Public License for more details.                         |
+   |                                                                      |
+   | You should have received a copy of both licenses referred to here.   |
+   | If you did not, or have any questions about PHP licensing, please    |
+   | contact core@php.net.                                                |
+   +----------------------------------------------------------------------+
+   | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca>                       |
+   |          Stig Bakken <ssb@guardian.no>                               |
+   |          Jim Winstead <jimw@php.net>                                 |
+   +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+/* gd 1.2 is copyright 1994, 1995, Quest Protein Database Center, 
+   Cold Spring Harbor Labs. */
+
+/* Note that there is no code from the gd package in this file */
+#ifdef THREAD_SAFE
+# include "tls.h"
+#endif
+#include "php.h"
+#include "head.h"
+#include <math.h>
+#include "php3_gd.h"
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#if WIN32|WINNT
+# include <io.h>
+# include <fcntl.h>
+#endif
+
+#if HAVE_LIBGD
+#include <gd.h>
+#include <gdfontt.h>  /* 1 Tiny font */
+#include <gdfonts.h>  /* 2 Small font */
+#include <gdfontmb.h> /* 3 Medium bold font */
+#include <gdfontl.h>  /* 4 Large font */
+#include <gdfontg.h>  /* 5 Giant font */
+#if HAVE_LIBTTF
+# if PHP_31
+#  include "gdttf.h"
+# else
+#  include "functions/gdttf.h"
+# endif
+#endif
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#if HAVE_LIBTTF
+static void php3_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int);
+#endif
+
+#ifdef THREAD_SAFE
+DWORD GDlibTls;
+static int numthreads=0;
+void *gdlib_mutex=NULL;
+
+typedef struct gdlib_global_struct{
+       int le_gd;
+       int le_gd_font;
+} gdlib_global_struct;
+
+# define GD_GLOBAL(a) gdlib_globals->a
+# define GD_TLS_VARS gdlib_global_struct *gdlib_globals = TlsGetValue(GDlibTls);
+
+#else
+#  define GD_GLOBAL(a) a
+#  define GD_TLS_VARS
+int le_gd;
+int le_gd_font;
+#endif
+
+function_entry gd_functions[] = {
+       {"imagearc",                            php3_imagearc,                          NULL},
+       {"imagechar",                           php3_imagechar,                         NULL},
+       {"imagecharup",                         php3_imagecharup,                       NULL},
+       {"imagecolorallocate",          php3_imagecolorallocate,        NULL},
+       {"imagecolorat",                php3_imagecolorat,              NULL},
+       {"imagecolorclosest",           php3_imagecolorclosest,         NULL},
+       {"imagecolordeallocate",        php3_imagecolordeallocate,      NULL},
+       {"imagecolorresolve",           php3_imagecolorresolve,         NULL},
+       {"imagecolorexact",                     php3_imagecolorexact,           NULL},
+       {"imagecolorset",               php3_imagecolorset,             NULL},
+       {"imagecolortransparent",       php3_imagecolortransparent,     NULL},
+       {"imagecolorstotal",            php3_imagecolorstotal,          NULL},
+       {"imagecolorsforindex",         php3_imagecolorsforindex,       NULL},
+       {"imagecopy",                   php3_imagecopy,                 NULL},
+       {"imagecopyresized",            php3_imagecopyresized,          NULL},
+       {"imagecreate",                         php3_imagecreate,                       NULL},
+       {"imagecreatefromgif",          php3_imagecreatefromgif,        NULL},
+       {"imagedestroy",                        php3_imagedestroy,                      NULL},
+       {"imagefill",                           php3_imagefill,                         NULL},
+       {"imagefilledpolygon",          php3_imagefilledpolygon,        NULL},
+       {"imagefilledrectangle",        php3_imagefilledrectangle,      NULL},
+       {"imagefilltoborder",           php3_imagefilltoborder,         NULL},
+       {"imagefontwidth",                      php3_imagefontwidth,            NULL},
+       {"imagefontheight",                     php3_imagefontheight,           NULL},
+       {"imagegif",                            php3_imagegif,                          NULL},
+       {"imageinterlace",                      php3_imageinterlace,            NULL},
+       {"imageline",                           php3_imageline,                         NULL},
+       {"imageloadfont",                       php3_imageloadfont,                     NULL},
+       {"imagepolygon",                        php3_imagepolygon,                      NULL},
+       {"imagerectangle",                      php3_imagerectangle,            NULL},
+       {"imagesetpixel",                       php3_imagesetpixel,                     NULL},
+       {"imagestring",                         php3_imagestring,                       NULL},
+       {"imagestringup",                       php3_imagestringup,                     NULL},
+       {"imagesx",                                     php3_imagesxfn,                         NULL},
+       {"imagesy",                                     php3_imagesyfn,                         NULL},
+       {"imagedashedline",                     php3_imagedashedline,           NULL},
+#if HAVE_LIBTTF
+       {"imagettfbbox",                        php3_imagettfbbox,                      NULL},
+       {"imagettftext",                        php3_imagettftext,                      NULL},
+#endif
+       {NULL, NULL, NULL}
+};
+
+php3_module_entry gd_module_entry = {
+       "gd", gd_functions, php3_minit_gd, php3_mend_gd, NULL, NULL, php3_info_gd, STANDARD_MODULE_PROPERTIES
+};
+
+#if COMPILE_DL
+# if PHP_31
+#  include "../phpdl.h"
+# else
+#  include "dl/phpdl.h"
+# endif
+DLEXPORT php3_module_entry *get_module(void) { return &gd_module_entry; }
+#endif
+
+
+#define PolyMaxPoints 256
+
+
+int php3_minit_gd(INIT_FUNC_ARGS)
+{
+#if defined(THREAD_SAFE)
+       gdlib_global_struct *gdlib_globals;
+       PHP3_MUTEX_ALLOC(gdlib_mutex);
+       PHP3_MUTEX_LOCK(gdlib_mutex);
+       numthreads++;
+       if (numthreads==1){
+               if (!PHP3_TLS_PROC_STARTUP(GDlibTls)){
+                       PHP3_MUTEX_UNLOCK(gdlib_mutex);
+                       PHP3_MUTEX_FREE(gdlib_mutex);
+                       return FAILURE;
+               }
+       }
+       PHP3_MUTEX_UNLOCK(gdlib_mutex);
+       if(!PHP3_TLS_THREAD_INIT(GDlibTls,gdlib_globals,gdlib_global_struct)){
+               PHP3_MUTEX_FREE(gdlib_mutex);
+               return FAILURE;
+       }
+#endif
+       GD_GLOBAL(le_gd) = register_list_destructors(gdImageDestroy, NULL);
+       GD_GLOBAL(le_gd_font) = register_list_destructors(php3_free_gd_font, NULL);
+       return SUCCESS;
+}
+
+void php3_info_gd(void) {
+       /* need to use a PHPAPI function here because it is external module in windows */
+#if HAVE_LIBGD13
+       php3_printf("Version 1.3");
+#else
+       php3_printf("Version 1.2");
+#endif
+#if HAVE_LIBTTF
+       php3_printf(" with FreeType support");
+#endif
+}
+
+int php3_mend_gd(void){
+       GD_TLS_VARS;
+#ifdef THREAD_SAFE
+       PHP3_TLS_THREAD_FREE(gdlib_globals);
+       PHP3_MUTEX_LOCK(gdlib_mutex);
+       numthreads--;
+       if (numthreads<1) {
+               PHP3_TLS_PROC_SHUTDOWN(GDlibTls);
+               PHP3_MUTEX_UNLOCK(gdlib_mutex);
+               PHP3_MUTEX_FREE(gdlib_mutex);
+               return SUCCESS;
+       }
+       PHP3_MUTEX_UNLOCK(gdlib_mutex);
+#endif
+       return SUCCESS;
+}
+
+/********************************************************************/
+/* gdImageColorResolve is a replacement for the old fragment:       */
+/*                                                                  */
+/*      if ((color=gdImageColorExact(im,R,G,B)) < 0)                */
+/*        if ((color=gdImageColorAllocate(im,R,G,B)) < 0)           */
+/*          color=gdImageColorClosest(im,R,G,B);                    */
+/*                                                                  */
+/* in a single function                                             */
+
+int
+gdImageColorResolve(gdImagePtr im, int r, int g, int b)
+{
+       int c;
+       int ct = -1;
+       int op = -1;
+       long rd, gd, bd, dist;
+       long mindist = 3*255*255;  /* init to max poss dist */
+
+       for (c = 0; c < im->colorsTotal; c++) {
+               if (im->open[c]) {
+                       op = c;             /* Save open slot */
+                       continue;           /* Color not in use */
+               }
+               rd = (long)(im->red  [c] - r);
+               gd = (long)(im->green[c] - g);
+               bd = (long)(im->blue [c] - b);
+               dist = rd * rd + gd * gd + bd * bd;
+               if (dist < mindist) {
+                       if (dist == 0) {
+                               return c;       /* Return exact match color */
+                       }
+                       mindist = dist;
+                       ct = c;
+               }
+       }
+       /* no exact match.  We now know closest, but first try to allocate exact */
+       if (op == -1) {
+               op = im->colorsTotal;
+               if (op == gdMaxColors) {    /* No room for more colors */
+                       return ct;          /* Return closest available color */
+               }
+               im->colorsTotal++;
+       }
+       im->red  [op] = r;
+       im->green[op] = g;
+       im->blue [op] = b;
+       im->open [op] = 0;
+       return op;                  /* Return newly allocated color */
+}
+
+void php3_free_gd_font(gdFontPtr fp)
+{
+       if (fp->data) {
+               efree(fp->data);
+       }
+       efree(fp);
+}
+
+/* {{{ proto int imageloadfont(string filename)
+Load a new font */
+void php3_imageloadfont(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *file;
+       int hdr_size = sizeof(gdFont) - sizeof(char *);
+       int ind, body_size, n=0, b;
+       gdFontPtr font;
+       FILE *fp;
+       int issock=0, socketd=0;
+       GD_TLS_VARS;
+
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &file) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_string(file);
+
+#if WIN32|WINNT
+       fp = fopen(file->value.str.val, "rb");
+#else
+       fp = php3_fopen_wrapper(file->value.str.val, "r", IGNORE_PATH|IGNORE_URL_WIN, &issock, &socketd);
+#endif
+       if (fp == NULL) {
+               php3_error(E_WARNING, "ImageFontLoad: unable to open file");
+               RETURN_FALSE;
+       }
+
+       /* Only supports a architecture-dependent binary dump format
+        * at the moment.
+        * The file format is like this on machines with 32-byte integers:
+        *
+        * byte 0-3:   (int) number of characters in the font
+        * byte 4-7:   (int) value of first character in the font (often 32, space)
+        * byte 8-11:  (int) pixel width of each character
+        * byte 12-15: (int) pixel height of each character
+        * bytes 16-:  (char) array with character data, one byte per pixel
+        *                    in each character, for a total of 
+        *                    (nchars*width*height) bytes.
+        */
+       font = (gdFontPtr)emalloc(sizeof(gdFont));
+       b = 0;
+       while (b < hdr_size && (n = fread(&font[b], 1, hdr_size - b, fp)))
+               b += n;
+       if (!n) {
+               fclose(fp);
+               efree(font);
+               if (feof(fp)) {
+                       php3_error(E_WARNING, "ImageFontLoad: end of file while reading header");
+               } else {
+                       php3_error(E_WARNING, "ImageFontLoad: error while reading header");
+               }
+               RETURN_FALSE;
+       }
+       body_size = font->w * font->h * font->nchars;
+       font->data = emalloc(body_size);
+       b = 0;
+       while (b < body_size && (n = fread(&font->data[b], 1, body_size - b, fp)))
+               b += n;
+       if (!n) {
+               fclose(fp);
+               efree(font->data);
+               efree(font);
+               if (feof(fp)) {
+                       php3_error(E_WARNING, "ImageFontLoad: end of file while reading body");
+               } else {
+                       php3_error(E_WARNING, "ImageFontLoad: error while reading body");
+               }
+               RETURN_FALSE;
+       }
+       fclose(fp);
+
+       /* Adding 5 to the font index so we will never have font indices
+        * that overlap with the old fonts (with indices 1-5).  The first
+        * list index given out is always 1.
+        */
+       ind = 5 + php3_list_insert(font, GD_GLOBAL(le_gd_font));
+
+       RETURN_LONG(ind);
+}
+/* }}} */
+
+/* {{{ proto int imagecreate(int x_size, int y_size)
+Create a new image */
+void php3_imagecreate(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *x_size, *y_size;
+       int ind;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &x_size, &y_size) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(x_size);
+       convert_to_long(y_size);
+
+       im = gdImageCreate(x_size->value.lval, y_size->value.lval);
+       ind = php3_list_insert(im, GD_GLOBAL(le_gd));           
+
+       RETURN_LONG(ind);
+}
+/* }}} */
+
+/* {{{ proto int imagecreatefromgif(string filename)
+Create a new image from file or URL */
+void php3_imagecreatefromgif (INTERNAL_FUNCTION_PARAMETERS) {
+       pval *file;
+       int ind;
+       gdImagePtr im;
+       char *fn=NULL;
+       FILE *fp;
+       int issock=0, socketd=0;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &file) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_string(file);
+
+       fn = file->value.str.val;
+
+#if WIN32|WINNT
+       fp = fopen(file->value.str.val, "rb");
+#else
+       fp = php3_fopen_wrapper(file->value.str.val, "r", IGNORE_PATH|IGNORE_URL_WIN, &issock, &socketd);
+#endif
+       if (!fp) {
+               php3_strip_url_passwd(fn);
+               php3_error(E_WARNING,
+                                       "ImageCreateFromGif: Unable to open %s for reading", fn);
+               RETURN_FALSE;
+       }
+
+       im = gdImageCreateFromGif (fp);
+
+       fflush(fp);
+       fclose(fp);
+
+       ind = php3_list_insert(im, GD_GLOBAL(le_gd));
+
+       RETURN_LONG(ind);
+}
+/* }}} */
+
+/* {{{ proto int imagedestroy(int im)
+Destroy an image */
+void php3_imagedestroy(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind;
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &imgind) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(imgind);
+
+       php3_list_delete(imgind->value.lval);
+
+       RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto int imagecolorallocate(int im, int red, int green, int blue)
+Allocate a color for an image */
+void php3_imagecolorallocate(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *red, *green, *blue;
+       int ind, ind_type;
+       int col;
+       int r, g, b;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 || getParameters(ht, 4, &imgind, &red,
+                                                                                       &green, &blue) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(red);
+       convert_to_long(green);
+       convert_to_long(blue);
+       
+       ind = imgind->value.lval;
+       r = red->value.lval;
+       g = green->value.lval;
+       b = blue->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorAllocate: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+       col = gdImageColorAllocate(im, r, g, b);
+       RETURN_LONG(col);
+}
+/* }}} */
+
+/* im, x, y */
+/* {{{ proto int imagecolorat(int im, int x, int y)
+Get the index of the color of a pixel */
+void php3_imagecolorat(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *x, *y;
+       int ind, ind_type;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 3 || getParameters(ht, 3, &imgind, &x, &y) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(x);
+       convert_to_long(y);
+       
+       ind = imgind->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorAt: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+       if (gdImageBoundsSafe(im, x->value.lval, y->value.lval)) {
+#if HAVE_LIBGD13
+               RETURN_LONG(im->pixels[y->value.lval][x->value.lval]);
+#else
+               RETURN_LONG(im->pixels[x->value.lval][y->value.lval]);
+#endif
+       }
+       else {
+               RETURN_FALSE;
+       }
+}
+/* }}} */
+
+/* {{{ proto int imagecolorclosest(int im, int red, int green, int blue)
+Get the index of the closest color to the specified color */
+void php3_imagecolorclosest(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *red, *green, *blue;
+       int ind, ind_type;
+       int col;
+       int r, g, b;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 || getParameters(ht, 4, &imgind, &red,
+                                                                                       &green, &blue) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(red);
+       convert_to_long(green);
+       convert_to_long(blue);
+       
+       ind = imgind->value.lval;
+       r = red->value.lval;
+       g = green->value.lval;
+       b = blue->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorClosest: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+       col = gdImageColorClosest(im, r, g, b);
+       RETURN_LONG(col);
+}
+/* }}} */
+
+/* {{{ proto int imagecolordeallocate(int im, int index)
+De-allocate a color for an image */
+void php3_imagecolordeallocate(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *index;
+       int ind, ind_type, col;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &imgind, &index) == FAILURE) {
+       WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(imgind);
+       convert_to_long(index);
+       ind = imgind->value.lval;
+       col = index->value.lval;
+
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorDeallocate: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       if (col >= 0 && col < gdImageColorsTotal(im)) {
+               gdImageColorDeallocate(im, col);
+               RETURN_TRUE;
+        }
+        else {
+                php3_error(E_WARNING, "Color index out of range");
+                RETURN_FALSE;
+        }
+}
+/* }}} */
+
+/* {{{ proto int imagecolorresolve(int im, int red, int green, int blue)
+Get the index of the specified color or its closest possible alternative */
+void php3_imagecolorresolve(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *red, *green, *blue;
+       int ind, ind_type;
+       int col;
+       int r, g, b;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 || getParameters(ht, 4, &imgind, &red,
+                                                                                       &green, &blue) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(red);
+       convert_to_long(green);
+       convert_to_long(blue);
+       
+       ind = imgind->value.lval;
+       r = red->value.lval;
+       g = green->value.lval;
+       b = blue->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorResolve: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+       col = gdImageColorResolve(im, r, g, b);
+       RETURN_LONG(col);
+}
+/* }}} */
+
+/* {{{ proto int imagecolorexact(int im, int red, int green, int blue)
+Get the index of the specified color */
+void php3_imagecolorexact(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *red, *green, *blue;
+       int ind, ind_type;
+       int col;
+       int r, g, b;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 || getParameters(ht, 4, &imgind, &red,
+                                                                                       &green, &blue) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(red);
+       convert_to_long(green);
+       convert_to_long(blue);
+       
+       ind = imgind->value.lval;
+       r = red->value.lval;
+       g = green->value.lval;
+       b = blue->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorExact: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+       col = gdImageColorExact(im, r, g, b);
+       RETURN_LONG(col);
+}
+/* }}} */
+
+/* {{{ proto int imagecolorset(int im, int col, int red, int green, int blue)
+Set the color for the specified palette index */
+void php3_imagecolorset(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *color, *red, *green, *blue;
+       int ind, ind_type;
+       int col;
+       int r, g, b;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 5 || getParameters(ht, 5, &imgind, &color, &red, &green, &blue) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(color);
+       convert_to_long(red);
+       convert_to_long(green);
+       convert_to_long(blue);
+       
+       ind = imgind->value.lval;
+       col = color->value.lval;
+       r = red->value.lval;
+       g = green->value.lval;
+       b = blue->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorSet: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+       if (col >= 0 && col < gdImageColorsTotal(im)) {
+               im->red[col] = r;
+               im->green[col] = g;
+               im->blue[col] = b;
+       }
+       else {
+               RETURN_FALSE;
+       }
+}
+/* }}} */
+
+/* {{{ proto array imagecolorsforindex(int im, int col)
+Get the colors for an index */
+void php3_imagecolorsforindex(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *index;
+       int col, ind, ind_type;
+       gdImagePtr im;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &imgind, &index) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       
+       convert_to_long(imgind);
+       convert_to_long(index);
+       ind = imgind->value.lval;
+       col = index->value.lval;
+       
+       im = php3_list_find(ind, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageColorsForIndex: Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       if (col >= 0 && col < gdImageColorsTotal(im)) {
+               if (array_init(return_value) == FAILURE) {
+                       RETURN_FALSE;
+               }
+               add_assoc_long(return_value,"red",im->red[col]);
+               add_assoc_long(return_value,"green",im->green[col]);
+               add_assoc_long(return_value,"blue",im->blue[col]);
+       }
+       else {
+               php3_error(E_WARNING, "Color index out of range");
+               RETURN_FALSE;
+       }
+}
+/* }}} */
+
+/* {{{ proto int imagegif(int im, string filename)
+Output image to browser or file */
+void php3_imagegif (INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imgind, *file;
+       gdImagePtr im;
+       char *fn=NULL;
+       FILE *fp;
+       int argc;
+       int ind_type;
+       int output=1;
+       GD_TLS_VARS;
+
+       argc = ARG_COUNT(ht);
+       if (argc < 1 || argc > 2 || getParameters(ht, argc, &imgind, &file) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(imgind);
+
+       if (argc == 2) {
+               convert_to_string(file);
+               fn = file->value.str.val;
+               if (!fn || fn == empty_string || _php3_check_open_basedir(fn)) {
+                       php3_error(E_WARNING, "ImageGif: Invalid filename");
+                       RETURN_FALSE;
+               }
+       }
+
+       im = php3_list_find(imgind->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "ImageGif: unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       if (argc == 2) {
+               fp = fopen(fn, "wb");
+               if (!fp) {
+                       php3_error(E_WARNING, "ImageGif: unable to open %s for writing", fn);
+                       RETURN_FALSE;
+               }
+               gdImageGif (im,fp);
+               fflush(fp);
+               fclose(fp);
+       }
+       else {
+               int   b;
+               FILE *tmp;
+               char  buf[4096];
+
+               tmp = tmpfile();
+               if (tmp == NULL) {
+                       php3_error(E_WARNING, "Unable to open temporary file");
+                       RETURN_FALSE;
+               }
+
+               output = php3_header();
+
+               if (output) {
+                       gdImageGif (im, tmp);
+                       fseek(tmp, 0, SEEK_SET);
+#if APACHE && defined(CHARSET_EBCDIC)
+                       /* This is a binary file already: avoid EBCDIC->ASCII conversion */
+                       ap_bsetflag(php3_rqst->connection->client, B_EBCDIC2ASCII, 0);
+#endif
+                       while ((b = fread(buf, 1, sizeof(buf), tmp)) > 0) {
+                               php3_write(buf, b);
+                       }
+               }
+
+               fclose(tmp);
+               /* the temporary file is automatically deleted */
+       }
+
+       RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto int imagesetpixel(int im, int x, int y, int col)
+Set a single pixel */
+void php3_imagesetpixel(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *imarg, *xarg, *yarg, *colarg;
+       gdImagePtr im;
+       int col, y, x;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 ||
+               getParameters(ht, 4, &imarg, &xarg, &yarg, &colarg) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(imarg);
+       convert_to_long(xarg);
+       convert_to_long(yarg);
+       convert_to_long(colarg);
+
+       col = colarg->value.lval;
+       y = yarg->value.lval;
+       x = xarg->value.lval;
+
+       im = php3_list_find(imarg->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageSetPixel(im,x,y,col);
+
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* im, x1, y1, x2, y2, col */
+/* {{{ proto int imageline(int im, int x1, int y1, int x2, int y2, int col)
+Draw a line */
+void php3_imageline(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *COL, *X1, *Y1, *X2, *Y2;
+       gdImagePtr im;
+       int col, y2, x2, y1, x1;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 6 ||
+               getParameters(ht, 6, &IM, &X1, &Y1, &X2, &Y2, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(X1);
+       convert_to_long(Y1);
+       convert_to_long(X2);
+       convert_to_long(Y2);
+       convert_to_long(COL);
+
+       x1 = X1->value.lval;
+       y1 = Y1->value.lval;
+       x2 = X2->value.lval;
+       y2 = Y2->value.lval;
+       col = COL->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageLine(im,x1,y1,x2,y2,col);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* {{{ proto int imagedashedline(int im, int x1, int y1, int x2, int y2, int col)
+Draw a dashed line */
+void php3_imagedashedline(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *COL, *X1, *Y1, *X2, *Y2;
+       gdImagePtr im;
+       int col, y2, x2, y1, x1;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 6 || getParameters(ht, 6, &IM, &X1, &Y1, &X2, &Y2, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(X1);
+       convert_to_long(Y1);
+       convert_to_long(X2);
+       convert_to_long(Y2);
+       convert_to_long(COL);
+
+       x1 = X1->value.lval;
+       y1 = Y1->value.lval;
+       x2 = X2->value.lval;
+       y2 = Y2->value.lval;
+       col = COL->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageDashedLine(im,x1,y1,x2,y2,col);
+       RETURN_TRUE;
+}
+/* }}} */
+
+/* im, x1, y1, x2, y2, col */
+/* {{{ proto int imagerectangle(int im, int x1, int y1, int x2, int y2, int col)
+Draw a rectangle */
+void php3_imagerectangle(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *COL, *X1, *Y1, *X2, *Y2;
+       gdImagePtr im;
+       int col, y2, x2, y1, x1;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 6 ||
+               getParameters(ht, 6, &IM, &X1, &Y1, &X2, &Y2, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(X1);
+       convert_to_long(Y1);
+       convert_to_long(X2);
+       convert_to_long(Y2);
+       convert_to_long(COL);
+
+       x1 = X1->value.lval;
+       y1 = Y1->value.lval;
+       x2 = X2->value.lval;
+       y2 = Y2->value.lval;
+       col = COL->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageRectangle(im,x1,y1,x2,y2,col);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* im, x1, y1, x2, y2, col */
+/* {{{ proto int imagefilledrectangle(int im, int x1, int y1, int x2, int y2, int col)
+Draw a filled rectangle */
+void php3_imagefilledrectangle(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *COL, *X1, *Y1, *X2, *Y2;
+       gdImagePtr im;
+       int col, y2, x2, y1, x1;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 6 ||
+               getParameters(ht, 6, &IM, &X1, &Y1, &X2, &Y2, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(X1);
+       convert_to_long(Y1);
+       convert_to_long(X2);
+       convert_to_long(Y2);
+       convert_to_long(COL);
+
+       x1 = X1->value.lval;
+       y1 = Y1->value.lval;
+       x2 = X2->value.lval;
+       y2 = Y2->value.lval;
+       col = COL->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageFilledRectangle(im,x1,y1,x2,y2,col);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* {{{ proto int imagearc(int im, int cx, int cy, int w, int h, int s, int e, int col)
+Draw a partial ellipse */
+void php3_imagearc(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *COL, *E, *ST, *H, *W, *CY, *CX, *IM;
+       gdImagePtr im;
+       int col, e, st, h, w, cy, cx;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 8 ||
+               getParameters(ht, 8, &IM, &CX, &CY, &W, &H, &ST, &E, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(CX);
+       convert_to_long(CY);
+       convert_to_long(W);
+       convert_to_long(H);
+       convert_to_long(ST);
+       convert_to_long(E);
+       convert_to_long(COL);
+
+       col = COL->value.lval;
+       e = E->value.lval;
+       st = ST->value.lval;
+       h = H->value.lval;
+       w = W->value.lval;
+       cy = CY->value.lval;
+       cx = CX->value.lval;
+
+       if (e < 0) {
+               e %= 360;
+       }
+       if (st < 0) {
+               st %= 360;
+       }
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageArc(im,cx,cy,w,h,st,e,col);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* im, x, y, border, col */
+/* {{{ proto int imagefilltoborder(int im, int x, int y, int border, int col)
+Flood fill to specific color */
+void php3_imagefilltoborder(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *X, *Y, *BORDER, *COL;
+       gdImagePtr im;
+       int col, border, y, x;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 5 ||
+               getParameters(ht, 5, &IM, &X, &Y, &BORDER, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(X);
+       convert_to_long(Y);
+       convert_to_long(BORDER);
+       convert_to_long(COL);
+
+       col = COL->value.lval;
+       border = BORDER->value.lval;
+       y = Y->value.lval;
+       x = X->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageFillToBorder(im,x,y,border,col);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* im, x, y, col */
+/* {{{ proto int imagefill(int im, int x, int y, int col)
+Flood fill */
+void php3_imagefill(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *X, *Y, *COL;
+       gdImagePtr im;
+       int col, y, x;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 ||
+               getParameters(ht, 4, &IM, &X, &Y, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(X);
+       convert_to_long(Y);
+       convert_to_long(COL);
+
+       col = COL->value.lval;
+       y = Y->value.lval;
+       x = X->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageFill(im,x,y,col);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* {{{ proto int imagecolorstotal(int im)
+Find out the number of colors in an image's palette */
+void php3_imagecolorstotal(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM;
+       gdImagePtr im;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &IM) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       convert_to_long(IM);
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       RETURN_LONG(gdImageColorsTotal(im));
+}
+/* }}} */      
+
+/* im, col */
+/* {{{ proto int imagecolortransparent(int im [, int col])
+Define a color as transparent */
+void php3_imagecolortransparent(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *COL = NULL;
+       gdImagePtr im;
+       int col;
+       int ind_type;
+       GD_TLS_VARS;
+
+       switch(ARG_COUNT(ht)) {
+       case 1:
+               if (getParameters(ht, 1, &IM) == FAILURE) {
+                       WRONG_PARAM_COUNT;
+               }
+               break;
+       case 2:
+               if (getParameters(ht, 2, &IM, &COL) == FAILURE) {
+                       WRONG_PARAM_COUNT;
+               }
+               convert_to_long(COL);
+               break;
+       default:
+               WRONG_PARAM_COUNT;
+       }
+       convert_to_long(IM);
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       if (COL != NULL) {
+               col = COL->value.lval;
+               gdImageColorTransparent(im,col);
+       }
+       col = gdImageGetTransparent(im);
+       RETURN_LONG(col);
+}
+/* }}} */      
+
+/* im, interlace */
+/* {{{ proto int imageinterlace(int im [, int interlace])
+Enable or disable interlace */
+void php3_imageinterlace(INTERNAL_FUNCTION_PARAMETERS) {
+       pval *IM, *INT = NULL;
+       gdImagePtr im;
+       int interlace;
+       int ind_type;
+       GD_TLS_VARS;
+
+       switch(ARG_COUNT(ht)) {
+       case 1:
+               if (getParameters(ht, 1, &IM) == FAILURE) {
+                       WRONG_PARAM_COUNT;
+               }
+               break;
+       case 2:
+               if (getParameters(ht, 2, &IM, &INT) == FAILURE) {
+                       WRONG_PARAM_COUNT;
+               }
+               convert_to_long(INT);
+               break;
+       default:
+               WRONG_PARAM_COUNT;
+       }
+       convert_to_long(IM);
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       if (INT != NULL) {
+               interlace = INT->value.lval;
+               gdImageInterlace(im,interlace);
+       }
+       interlace = gdImageGetInterlaced(im);
+       RETURN_LONG(interlace);
+}
+/* }}} */      
+
+/* arg = 0  normal polygon
+   arg = 1  filled polygon */
+/* im, points, num_points, col */
+static void _php3_imagepolygon(INTERNAL_FUNCTION_PARAMETERS, int filled) {
+       pval *IM, *POINTS, *NPOINTS, *COL, *var;
+       gdImagePtr im;
+       gdPoint points[PolyMaxPoints];  
+       int npoints, col, nelem, i;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 4 ||
+               getParameters(ht, 4, &IM, &POINTS, &NPOINTS, &COL) == FAILURE)
+       {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(NPOINTS);
+       convert_to_long(COL);
+
+       npoints = NPOINTS->value.lval;
+       col = COL->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       if (POINTS->type != IS_ARRAY) {
+               php3_error(E_WARNING, "2nd argument to imagepolygon not an array");
+               RETURN_FALSE;
+       }
+
+/*
+    ** we shouldn't need this check, should we? **
+
+    if (!ParameterPassedByReference(ht, 2)) {
+        php3_error(E_WARNING, "2nd argument to imagepolygon not passed by reference");
+               RETURN_FALSE;
+    }
+*/
+
+       nelem = _php3_hash_num_elements(POINTS->value.ht);
+       if (nelem < 6) {
+               php3_error(E_WARNING,
+                                       "you must have at least 3 points in your array");
+               RETURN_FALSE;
+       }
+
+       if (nelem < npoints * 2) {
+               php3_error(E_WARNING,
+                                       "trying to use %d points in array with only %d points",
+                                       npoints, nelem/2);
+               RETURN_FALSE;
+       }
+
+       if (npoints > PolyMaxPoints) {
+               php3_error(E_WARNING, "maximum %d points", PolyMaxPoints);
+               RETURN_FALSE;
+       }
+
+       for (i = 0; i < npoints; i++) {
+               if (_php3_hash_index_find(POINTS->value.ht, (i * 2), (void **)&var) == SUCCESS) {
+                       convert_to_long(var);
+                       points[i].x = var->value.lval;
+               }
+               if (_php3_hash_index_find(POINTS->value.ht, (i * 2) + 1, (void **)&var) == SUCCESS) {
+                       convert_to_long(var);
+                       points[i].y = var->value.lval;
+               }
+       }
+
+       if (filled) {
+               gdImageFilledPolygon(im, points, npoints, col);
+       }
+       else {
+               gdImagePolygon(im, points, npoints, col);
+       }
+
+       RETURN_TRUE;
+}
+
+
+/* {{{ proto int imagepolygon(int im, array point, int num_points, int col)
+Draw a polygon */
+void php3_imagepolygon(INTERNAL_FUNCTION_PARAMETERS)
+{
+       _php3_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
+}
+/* }}} */
+
+/* {{{ proto int imagefilledpolygon(int im, array point, int num_points, int col)
+Draw a filled polygon */
+void php3_imagefilledpolygon(INTERNAL_FUNCTION_PARAMETERS)
+{
+       _php3_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
+}
+/* }}} */
+
+
+static gdFontPtr _php3_find_gd_font(int size)
+{
+       gdFontPtr font;
+       int ind_type;
+       GD_TLS_VARS;
+
+       switch (size) {
+       case 1:
+                        font = gdFontTiny;
+                        break;
+       case 2:
+                        font = gdFontSmall;
+                        break;
+       case 3:
+                        font = gdFontMediumBold;
+                        break;
+       case 4:
+                        font = gdFontLarge;
+                        break;
+       case 5:
+                        font = gdFontGiant;
+                        break;
+           default:
+                       font = php3_list_find(size - 5, &ind_type);
+                        if (!font || ind_type != GD_GLOBAL(le_gd_font)) {
+                                 if (size < 1) {
+                                          font = gdFontTiny;
+                                 } else {
+                                          font = gdFontGiant;
+                                 }
+                        }
+                        break;
+       }
+
+       return font;
+}
+
+
+/*
+ * arg = 0  ImageFontWidth
+ * arg = 1  ImageFontHeight
+ */
+static void _php3_imagefontsize(INTERNAL_FUNCTION_PARAMETERS, int arg)
+{
+       pval *SIZE;
+       gdFontPtr font;
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &SIZE) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+       convert_to_long(SIZE);
+       font = _php3_find_gd_font(SIZE->value.lval);
+       RETURN_LONG(arg ? font->h : font->w);
+}
+
+
+/* {{{ proto int imagefontwidth(int font)
+Get font width */
+void php3_imagefontwidth(INTERNAL_FUNCTION_PARAMETERS)
+{
+       _php3_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
+}
+/* }}} */
+
+/* {{{ proto int imagefontheight(int font)
+Get font height */
+void php3_imagefontheight(INTERNAL_FUNCTION_PARAMETERS)
+{
+       _php3_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
+}
+/* }}} */
+
+
+/* workaround for a bug in gd 1.2 */
+void _php3_gdimagecharup(gdImagePtr im, gdFontPtr f, int x, int y, int c,
+                                                int color)
+{
+       int cx, cy, px, py, fline;
+       cx = 0;
+       cy = 0;
+       if ((c < f->offset) || (c >= (f->offset + f->nchars))) {
+               return;
+       }
+       fline = (c - f->offset) * f->h * f->w;
+       for (py = y; (py > (y - f->w)); py--) {
+               for (px = x; (px < (x + f->h)); px++) {
+                       if (f->data[fline + cy * f->w + cx]) {
+                               gdImageSetPixel(im, px, py, color);     
+                       }
+                       cy++;
+               }
+               cy = 0;
+               cx++;
+       }
+}
+
+/*
+ * arg = 0  ImageChar
+ * arg = 1  ImageCharUp
+ * arg = 2  ImageString
+ * arg = 3  ImageStringUp
+ */
+static void _php3_imagechar(INTERNAL_FUNCTION_PARAMETERS, int mode) {
+       pval *IM, *SIZE, *X, *Y, *C, *COL;
+       gdImagePtr im;
+       int ch = 0, col, x, y, size, i, l = 0;
+       unsigned char *string = NULL;
+       int ind_type;
+       gdFontPtr font;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 6 ||
+               getParameters(ht, 6, &IM, &SIZE, &X, &Y, &C, &COL) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(IM);
+       convert_to_long(SIZE);
+       convert_to_long(X);
+       convert_to_long(Y);
+       convert_to_string(C);
+       convert_to_long(COL);
+
+       col = COL->value.lval;
+
+       if (mode < 2) {
+               ch = (int)((unsigned char)*(C->value.str.val));
+       } else {
+               string = (unsigned char *) estrndup(C->value.str.val,C->value.str.len);
+               l = strlen(string);
+       }
+
+       y = Y->value.lval;
+       x = X->value.lval;
+       size = SIZE->value.lval;
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               if (string) {
+                       efree(string);
+               }
+               RETURN_FALSE;
+       }
+       
+       font = _php3_find_gd_font(size);
+
+       switch(mode) {
+       case 0:
+                       gdImageChar(im, font, x, y, ch, col);
+                       break;
+       case 1:
+                       _php3_gdimagecharup(im, font, x, y, ch, col);
+                       break;
+       case 2:
+                       for (i = 0; (i < l); i++) {
+                               gdImageChar(im, font, x, y, (int)((unsigned char)string[i]),
+                                                       col);
+                               x += font->w;
+                       }
+                       break;
+       case 3: {
+                       for (i = 0; (i < l); i++) {
+                               /* _php3_gdimagecharup(im, font, x, y, (int)string[i], col); */
+                               gdImageCharUp(im, font, x, y, (int)string[i], col);
+                               y -= font->w;
+                       }
+                       break;
+               }
+       }
+       if (string) {
+               efree(string);
+       }
+       RETURN_TRUE;
+}      
+
+/* {{{ proto int imagechar(int im, int font, int x, int y, string c, int col)
+Draw a character */ 
+void php3_imagechar(INTERNAL_FUNCTION_PARAMETERS) {
+       _php3_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
+}
+/* }}} */
+
+/* {{{ proto int imagecharup(int im, int font, int x, int y, string c, int col)
+Draw a character rotated 90 degrees counter-clockwise */
+void php3_imagecharup(INTERNAL_FUNCTION_PARAMETERS) {
+       _php3_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
+}
+/* }}} */
+
+/* {{{ proto int imagestring(int im, int font, int x, int y, string str, int col)
+Draw a string horizontally */
+void php3_imagestring(INTERNAL_FUNCTION_PARAMETERS) {
+       _php3_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2);
+}
+/* }}} */
+
+/* {{{ proto int imagestringup(int im, int font, int x, int y, string str, int col)
+Draw a string vertically - rotated 90 degrees counter-clockwise */
+void php3_imagestringup(INTERNAL_FUNCTION_PARAMETERS) {
+       _php3_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);
+}
+/* }}} */
+
+/* {{{ proto int imagecopy(int dst_im, int src_im, int dstX, int dstY, int srcX, int srcY, int srcW, int srcH)
+Copy part of an image */ 
+void php3_imagecopy(INTERNAL_FUNCTION_PARAMETERS)
+{
+       pval *SIM, *DIM, *SX, *SY, *SW, *SH, *DX, *DY;
+       gdImagePtr im_dst;
+       gdImagePtr im_src;
+       int srcH, srcW, srcY, srcX, dstY, dstX;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 8 ||
+               getParameters(ht, 8, &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH)
+                                                == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(SIM);
+       convert_to_long(DIM);
+       convert_to_long(SX);
+       convert_to_long(SY);
+       convert_to_long(SW);
+       convert_to_long(SH);
+       convert_to_long(DX);
+       convert_to_long(DY);
+
+       srcX = SX->value.lval;
+       srcY = SY->value.lval;
+       srcH = SH->value.lval;
+       srcW = SW->value.lval;
+       dstX = DX->value.lval;
+       dstY = DY->value.lval;
+
+       im_src = php3_list_find(SIM->value.lval, &ind_type);
+       if (!im_src || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       im_dst = php3_list_find(DIM->value.lval, &ind_type);
+       if (!im_dst || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageCopy(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH);
+       RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto int imagecopyresized(int dst_im, int src_im, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH);
+Copy and resize part of an image */
+void php3_imagecopyresized(INTERNAL_FUNCTION_PARAMETERS)
+{
+       pval *SIM, *DIM, *SX, *SY, *SW, *SH, *DX, *DY, *DW, *DH;
+       gdImagePtr im_dst;
+       gdImagePtr im_src;
+       int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 10 ||
+               getParameters(ht, 10, &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH,
+                                         &SW, &SH) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long(SIM);
+       convert_to_long(DIM);
+       convert_to_long(SX);
+       convert_to_long(SY);
+       convert_to_long(SW);
+       convert_to_long(SH);
+       convert_to_long(DX);
+       convert_to_long(DY);
+       convert_to_long(DW);
+       convert_to_long(DH);
+
+       srcX = SX->value.lval;
+       srcY = SY->value.lval;
+       srcH = SH->value.lval;
+       srcW = SW->value.lval;
+       dstX = DX->value.lval;
+       dstY = DY->value.lval;
+       dstH = DH->value.lval;
+       dstW = DW->value.lval;
+
+       im_src = php3_list_find(SIM->value.lval, &ind_type);
+       if (!im_src || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       im_dst = php3_list_find(DIM->value.lval, &ind_type);
+       if (!im_dst || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       gdImageCopyResized(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH,
+                                          srcW, srcH);
+       RETURN_TRUE;
+}
+/* }}} */      
+
+/* {{{ proto int imagesx(int im)
+Get image width */
+void php3_imagesxfn(INTERNAL_FUNCTION_PARAMETERS)
+{
+       pval *IM;
+       gdImagePtr im;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &IM) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       RETURN_LONG(gdImageSX(im));
+}
+/* }}} */
+
+/* {{{ proto int imagesy(int im)
+Get image height */
+void php3_imagesyfn(INTERNAL_FUNCTION_PARAMETERS)
+{
+       pval *IM;
+       gdImagePtr im;
+       int ind_type;
+       GD_TLS_VARS;
+
+       if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &IM) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       im = php3_list_find(IM->value.lval, &ind_type);
+       if (!im || ind_type != GD_GLOBAL(le_gd)) {
+               php3_error(E_WARNING, "Unable to find image pointer");
+               RETURN_FALSE;
+       }
+
+       RETURN_LONG(gdImageSY(im));
+}
+/* }}} */
+
+#if HAVE_LIBTTF
+
+#define TTFTEXT_DRAW 0
+#define TTFTEXT_BBOX 1
+
+/* {{{ proto array imagettfbbox(int size, int angle, string font_file, string text)
+Give the bounding box of a text using TrueType fonts */
+void php3_imagettfbbox(INTERNAL_FUNCTION_PARAMETERS)
+{
+       php3_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX);
+}
+/* }}} */
+
+/* {{{ proto array imagettftext(int im, int size, int angle, int x, int y, int col, string font_file, string text)
+Write text to the image using a TrueType font */
+void php3_imagettftext(INTERNAL_FUNCTION_PARAMETERS)
+{
+       php3_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW);
+}
+/* }}} */
+
+static
+void php3_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int mode)
+{
+       pval *IM, *PTSIZE, *ANGLE, *X, *Y, *C, *FONTNAME, *COL;
+       gdImagePtr im;
+       int  col, x, y, l=0, i;
+       int brect[8];
+       double ptsize, angle;
+       unsigned char *string = NULL, *fontname = NULL;
+       int ind_type;
+       char                            *error;
+
+       GD_TLS_VARS;
+
+       if (mode == TTFTEXT_BBOX) {
+               if (ARG_COUNT(ht) != 4 || getParameters(ht, 4, &PTSIZE, &ANGLE, &FONTNAME, &C) == FAILURE) {
+                       WRONG_PARAM_COUNT;
+               }
+       } else {
+               if (ARG_COUNT(ht) != 8 || getParameters(ht, 8, &IM, &PTSIZE, &ANGLE, &X, &Y, &COL, &FONTNAME, &C) == FAILURE) {
+                       WRONG_PARAM_COUNT;
+               }
+       }
+
+       convert_to_double(PTSIZE);
+       convert_to_double(ANGLE);
+       convert_to_string(FONTNAME);
+       convert_to_string(C);
+       if (mode == TTFTEXT_BBOX) {
+              im = NULL;
+               col = x = y = -1;
+       } else {
+               convert_to_long(X);
+               convert_to_long(Y);
+               convert_to_long(IM);
+               convert_to_long(COL);
+               col = COL->value.lval;
+               y = Y->value.lval;
+               x = X->value.lval;
+              im = php3_list_find(IM->value.lval, &ind_type);
+              if (!im || ind_type != GD_GLOBAL(le_gd)) {
+                      php3_error(E_WARNING, "Unable to find image pointer");
+                      RETURN_FALSE;
+              }
+       }
+
+       ptsize = PTSIZE->value.dval;
+       angle = ANGLE->value.dval * (M_PI/180); /* convert to radians */
+
+       string = (unsigned char *) C->value.str.val;
+       l = strlen(string);
+       fontname = (unsigned char *) FONTNAME->value.str.val;
+
+       error = gdttf(im, brect, col, fontname, ptsize, angle, x, y, string);
+
+       if (error) {
+               php3_error(E_WARNING, error);
+               RETURN_FALSE;
+       }
+
+       
+       if (array_init(return_value) == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       /* return array with the text's bounding box */
+       for (i = 0; i < 8; i++) {
+               add_next_index_long(return_value, brect[i]);
+       }
+}
+#endif
+#endif
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/ext/gd/gdcache.c b/ext/gd/gdcache.c
new file mode 100644 (file)
index 0000000..3fd82de
--- /dev/null
@@ -0,0 +1,201 @@
+/* 
+ * $Id$
+ *
+ * Caches of pointers to user structs in which the least-recently-used 
+ * element is replaced in the event of a cache miss after the cache has 
+ * reached a given size.
+ *
+ * John Ellson  (ellson@lucent.com)  Oct 31, 1997
+ *
+ * Test this with:
+ *              gcc -o gdcache -g -Wall -DTEST gdcache.c
+ *
+ * The cache is implemented by a singly-linked list of elements
+ * each containing a pointer to a user struct that is being managed by
+ * the cache.
+ *
+ * The head structure has a pointer to the most-recently-used
+ * element, and elements are moved to this position in the list each
+ * time they are used.  The head also contains pointers to three
+ * user defined functions: 
+ *             - a function to test if a cached userdata matches some keydata 
+ *             - a function to provide a new userdata struct to the cache 
+ *          if there has been a cache miss.
+ *             - a function to release a userdata struct when it is
+ *          no longer being managed by the cache
+ *
+ * In the event of a cache miss the cache is allowed to grow up to
+ * a specified maximum size.  After the maximum size is reached then
+ * the least-recently-used element is discarded to make room for the 
+ * new.  The most-recently-returned value is always left at the 
+ * beginning of the list after retrieval.
+ *
+ * In the current implementation the cache is traversed by a linear
+ * search from most-recent to least-recent.  This linear search
+ * probably limits the usefulness of this implementation to cache
+ * sizes of a few tens of elements.
+ */
+
+/* This just seems unessacary */
+#if (WIN32|WINNT)
+#define HAVE_LIBTTF 1
+#else
+#include "config.h"
+#endif
+#if HAVE_LIBTTF
+
+#include "gdcache.h"
+
+/*********************************************************/
+/* implementation                                        */
+/*********************************************************/
+
+
+/* create a new cache */
+gdCache_head_t *
+gdCacheCreate(
+       int                                     size,
+       gdCacheTestFn_t         gdCacheTest,
+       gdCacheFetchFn_t        gdCacheFetch,
+       gdCacheReleaseFn_t      gdCacheRelease ) 
+{
+       gdCache_head_t *head; 
+
+       head = (gdCache_head_t *)malloc(sizeof(gdCache_head_t));
+       head->mru = NULL;
+       head->size = size;
+       head->gdCacheTest = gdCacheTest;
+       head->gdCacheFetch = gdCacheFetch;
+       head->gdCacheRelease = gdCacheRelease;
+       return head;
+}
+
+void
+gdCacheDelete( gdCache_head_t *head )
+{
+       gdCache_element_t *elem, *prev;
+
+       elem = head->mru;
+       while(elem) {
+               (*(head->gdCacheRelease))(elem->userdata);
+               prev = elem;
+               elem = elem->next;
+               free((char *)prev);
+       }
+       free((char *)head);
+}
+
+void *
+gdCacheGet( gdCache_head_t *head, void *keydata )
+{
+       int                             i=0;
+       gdCache_element_t *elem, *prev = NULL, *prevprev = NULL;
+       void                    *userdata;
+
+       elem = head->mru;
+       while(elem) {
+               if ((*(head->gdCacheTest))(elem->userdata, keydata)) {
+                       if (i) {  /* if not already most-recently-used */
+                               /* relink to top of list */
+                               prev->next = elem->next;
+                               elem->next = head->mru;
+                               head->mru = elem;
+                       }
+                       return elem->userdata;
+               }
+               prevprev = prev;
+               prev = elem;
+               elem = elem->next;
+               i++;
+       }
+       userdata = (*(head->gdCacheFetch))(&(head->error), keydata);
+       if (! userdata) {
+               /* if there was an error in the fetch then don't cache */
+               return NULL;
+       }
+       if (i < head->size) {  /* cache still growing - add new elem */
+               elem = (gdCache_element_t *)malloc(sizeof(gdCache_element_t));
+       }
+       else { /* cache full - replace least-recently-used */
+               /* preveprev becomes new end of list */
+               prevprev->next = NULL;
+               elem = prev;
+               (*(head->gdCacheRelease))(elem->userdata);
+       }
+       /* relink to top of list */
+       elem->next = head->mru;
+       head->mru = elem;
+       elem->userdata = userdata;
+       return userdata;
+}
+
+
+
+/*********************************************************/
+/* test stub                                             */
+/*********************************************************/
+
+
+#ifdef GDCACHE_TEST
+
+#include <stdio.h>
+
+typedef struct {
+       int key;
+       int value;
+} key_value_t;
+
+static int
+cacheTest( void *map, void *key )
+{
+       return (((key_value_t *)map)->key == *(int *)key);
+}
+
+static void *
+cacheFetch( char **error, void *key )
+{
+       key_value_t *map;
+
+       map = (key_value_t *)malloc(sizeof(key_value_t));
+       map->key = *(int *)key;
+       map->value = 3;
+
+       *error = NULL;
+       return (void *)map;
+}
+
+static void
+cacheRelease( void *map)
+{
+       free( (char *)map );
+}
+
+int
+main(char *argv[], int argc)
+{
+       gdCache_head_t  *cacheTable;
+       int                     elem, key;
+
+       cacheTable = gdCacheCreate(3, cacheTest, cacheFetch, cacheRelease);
+
+       key = 20;
+       elem = *(int *)gdCacheGet(cacheTable, &key);
+       key = 30;
+       elem = *(int *)gdCacheGet(cacheTable, &key);
+       key = 40;
+       elem = *(int *)gdCacheGet(cacheTable, &key);
+       key = 50;
+       elem = *(int *)gdCacheGet(cacheTable, &key);
+       key = 30;
+       elem = *(int *)gdCacheGet(cacheTable, &key);
+       key = 30;
+       elem = *(int *)gdCacheGet(cacheTable, &key);
+
+       gdCacheDelete(cacheTable);
+
+       return 0;
+}
+
+#endif
+
+#endif /* HAVE_LIBTTF */
diff --git a/ext/gd/gdcache.h b/ext/gd/gdcache.h
new file mode 100644 (file)
index 0000000..cdfbf71
--- /dev/null
@@ -0,0 +1,87 @@
+/* 
+ * $Id$ 
+ *
+ * Caches of pointers to user structs in which the least-recently-used 
+ * element is replaced in the event of a cache miss after the cache has 
+ * reached a given size.
+ *
+ * John Ellson  (ellson@lucent.com)  Oct 31, 1997
+ *
+ * Test this with:
+ *              gcc -o gdcache -g -Wall -DTEST gdcache.c
+ *
+ * The cache is implemented by a singly-linked list of elements
+ * each containing a pointer to a user struct that is being managed by
+ * the cache.
+ *
+ * The head structure has a pointer to the most-recently-used
+ * element, and elements are moved to this position in the list each
+ * time they are used.  The head also contains pointers to three
+ * user defined functions: 
+ *             - a function to test if a cached userdata matches some keydata 
+ *             - a function to provide a new userdata struct to the cache 
+ *          if there has been a cache miss.
+ *             - a function to release a userdata struct when it is
+ *          no longer being managed by the cache
+ *
+ * In the event of a cache miss the cache is allowed to grow up to
+ * a specified maximum size.  After the maximum size is reached then
+ * the least-recently-used element is discarded to make room for the 
+ * new.  The most-recently-returned value is always left at the 
+ * beginning of the list after retrieval.
+ *
+ * In the current implementation the cache is traversed by a linear
+ * search from most-recent to least-recent.  This linear search
+ * probably limits the usefulness of this implementation to cache
+ * sizes of a few tens of elements.
+ */
+
+/*********************************************************/
+/* header                                                */
+/*********************************************************/
+
+#ifndef _OSD_POSIX
+#include <malloc.h>
+#else
+#include <stdlib.h> /* BS2000/OSD defines malloc() & friends in stdlib.h */
+#endif
+#ifndef NULL
+#define NULL (void *)0
+#endif
+
+/* user defined function templates */
+typedef int (*gdCacheTestFn_t)(void *userdata, void *keydata);
+typedef void *(*gdCacheFetchFn_t)(char **error, void *keydata);
+typedef void (*gdCacheReleaseFn_t)(void *userdata);
+
+/* element structure */
+typedef struct gdCache_element_s gdCache_element_t;
+struct gdCache_element_s {
+       gdCache_element_t       *next;
+       void                    *userdata;
+};
+
+/* head structure */
+typedef struct gdCache_head_s gdCache_head_t;
+struct gdCache_head_s {
+       gdCache_element_t       *mru;
+       int                                     size;
+       char                            *error;
+       gdCacheTestFn_t         gdCacheTest;
+       gdCacheFetchFn_t        gdCacheFetch;
+       gdCacheReleaseFn_t      gdCacheRelease;
+};
+
+/* function templates */
+gdCache_head_t *
+gdCacheCreate(
+       int                                     size,
+       gdCacheTestFn_t         gdCacheTest,
+       gdCacheFetchFn_t        gdCacheFetch,
+       gdCacheReleaseFn_t      gdCacheRelease );
+
+void
+gdCacheDelete( gdCache_head_t *head );
+
+void *
+gdCacheGet( gdCache_head_t *head, void *keydata );
diff --git a/ext/gd/gdttf.c b/ext/gd/gdttf.c
new file mode 100644 (file)
index 0000000..39e93ed
--- /dev/null
@@ -0,0 +1,862 @@
+/* gd interface to freetype library         */
+/*                                          */
+/* John Ellson   ellson@lucent.com          */
+
+/* $Id$ */
+
+#if WIN32|WINNT
+#include "config.w32.h"
+#else
+#include "config.h"
+#endif
+#if HAVE_LIBTTF
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <gd.h>
+#include "gdttf.h"
+#include "gdcache.h"
+#include <freetype.h>
+
+/* number of fonts cached before least recently used is replaced */
+#define FONTCACHESIZE 6
+
+/* number of character glyphs cached per font before 
+       least-recently-used is replaced */
+#define GLYPHCACHESIZE 120
+
+/* number of bitmaps cached per glyph before 
+       least-recently-used is replaced */
+#define BITMAPCACHESIZE 8
+
+/* number of antialias color lookups cached */
+#define TWEENCOLORCACHESIZE 32
+
+/* ptsize below which anti-aliasing is ineffective */
+#define MINANTIALIASPTSIZE 0
+
+/* display resolution - (Not really.  This has to be 72 or hinting is wrong) */ 
+#define RESOLUTION 72
+
+/* Number of colors used for anti-aliasing */
+#define NUMCOLORS 4
+
+/* Line separation as a factor of font height.  
+      No space between if LINESPACE = 1.00 
+      Line separation will be rounded up to next pixel row*/
+#define LINESPACE 1.05
+
+#ifndef TRUE
+#define FALSE 0
+#define TRUE !FALSE
+#endif
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) ((a)<(b)?(a):(b))
+
+typedef struct {
+       char                            *fontname;      /* key */
+       double                          ptsize;         /* key */
+       double                          angle;          /* key */
+       double                          sin_a, cos_a;
+       TT_Engine           *engine;
+       TT_Face                         face;
+       TT_Face_Properties  properties;
+       TT_Instance                     instance;
+       TT_CharMap                      char_map;
+       TT_Matrix                       matrix;
+       TT_Instance_Metrics     imetrics;
+       gdCache_head_t          *glyphCache;
+} font_t;
+
+typedef struct {
+       char                            *fontname;      /* key */
+       double                          ptsize;         /* key */
+       double                          angle;          /* key */
+       TT_Engine                       *engine;
+} fontkey_t;
+
+typedef struct {
+       int                                     character;      /* key */
+       int                                     hinting;        /* key */
+       TT_Glyph                        glyph;
+       TT_Glyph_Metrics        metrics;
+       TT_Outline                      outline;
+       TT_Pos                          oldx, oldy;
+       TT_Raster_Map           Bit;
+       int                                     gray_render;
+       int                                     xmin, xmax, ymin, ymax;
+       gdCache_head_t          *bitmapCache;
+} glyph_t;
+
+typedef struct {
+       int                                     character;      /* key */
+       int                                     hinting;        /* key */
+       int                                     gray_render;
+       font_t                          *font;
+} glyphkey_t;
+
+typedef struct {
+       int                                     xoffset;        /* key */
+       int                                     yoffset;        /* key */
+       char                            *bitmap;
+} bitmap_t;
+
+typedef struct {
+       int                                     xoffset;        /* key */
+       int                                     yoffset;        /* key */
+       glyph_t                         *glyph;
+} bitmapkey_t;
+
+typedef struct { 
+    unsigned char       pixel;         /* key */
+    unsigned char       bgcolor;       /* key */
+    int                                        fgcolor;        /* key */ /* -ve means no antialias */
+    gdImagePtr          im;                    /* key */
+    unsigned char       tweencolor;
+} tweencolor_t;
+
+typedef struct {
+    unsigned char       pixel;      /* key */
+    unsigned char       bgcolor;    /* key */
+    int                                        fgcolor;    /* key */ /* -ve means no antialias */
+    gdImagePtr          im;                    /* key */
+} tweencolorkey_t;  
+
+/* forward declarations so that glyphCache can be initialized by font code */
+static int glyphTest ( void *element, void *key );
+static void *glyphFetch ( char **error, void *key );
+static void glyphRelease( void *element );
+
+/* forward declarations so that bitmapCache can be initialized by glyph code */
+static int bitmapTest ( void *element, void *key );
+static void *bitmapFetch ( char **error, void *key );
+static void bitmapRelease( void *element );
+
+/* local prototype */
+char *gdttfchar(gdImage *im, int fg, font_t *font, int x, int y, TT_F26Dot6 x1,  TT_F26Dot6 y1, TT_F26Dot6 *advance, TT_BBox **bbox, char **next);
+
+/* This prototype is missing from gd.h */
+/* FIXME  Since when does GD have this function???  My copy of 1.3 doesnt
+int gdImageColorResolve(gdImagePtr im, int r, int g, int b);
+*/
+
+/********************************************************************/
+/* gdImageColorResolve is a replacement for the old fragment:       */
+/*                                                                  */
+/*      if ((color=gdImageColorExact(im,R,G,B)) < 0)                */
+/*        if ((color=gdImageColorAllocate(im,R,G,B)) < 0)           */
+/*          color=gdImageColorClosest(im,R,G,B);                    */
+/*                                                                  */
+/* in a single function                                             */
+
+static int
+gdImageColorResolve(gdImagePtr im, int r, int g, int b)
+{
+       int c; 
+       int ct = -1;
+       int op = -1;
+       long rd, gd, bd, dist;
+       long mindist = 3*255*255;  /* init to max poss dist */
+
+       for (c = 0; c < im->colorsTotal; c++) {
+               if (im->open[c]) {
+                       op = c;                         /* Save open slot */
+                       continue;                       /* Color not in use */
+               }
+               rd = (long)(im->red  [c] - r);
+               gd = (long)(im->green[c] - g);
+               bd = (long)(im->blue [c] - b);
+               dist = rd * rd + gd * gd + bd * bd;
+               if (dist < mindist) {
+                       if (dist == 0) {
+                               return c;               /* Return exact match color */
+                       }
+                       mindist = dist;
+                       ct = c;
+               }
+       }       
+       /* no exact match.  We now know closest, but first try to allocate exact */
+       if (op == -1) {
+               op = im->colorsTotal;
+               if (op == gdMaxColors) {    /* No room for more colors */
+                       return ct;              /* Return closest available color */
+               }
+               im->colorsTotal++;
+       }
+       im->red  [op] = r;
+       im->green[op] = g;
+       im->blue [op] = b;
+       im->open [op] = 0;
+       return op;                                      /* Return newly allocated color */
+}
+
+/********************************************************************
+ * gdTcl_UtfToUniChar is borrowed from ...
+ */
+/*
+ * tclUtf.c --
+ *
+ *     Routines for manipulating UTF-8 strings.
+ *
+ * Copyright (c) 1997-1998 Sun Microsystems, Inc.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * SCCS: @(#) tclUtf.c 1.25 98/01/28 18:02:43
+ */
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * gdTcl_UtfToUniChar --
+ *
+ *     Extract the Tcl_UniChar represented by the UTF-8 string.  Bad
+ *     UTF-8 sequences are converted to valid Tcl_UniChars and processing
+ *     continues.  Equivalent to Plan 9 chartorune().
+ *
+ *     The caller must ensure that the source buffer is long enough that
+ *     this routine does not run off the end and dereference non-existent
+ *     memory looking for trail bytes.  If the source buffer is known to
+ *     be '\0' terminated, this cannot happen.  Otherwise, the caller
+ *     should call Tcl_UtfCharComplete() before calling this routine to
+ *     ensure that enough bytes remain in the string.
+ *
+ * Results:
+ *     *chPtr is filled with the Tcl_UniChar, and the return value is the
+ *     number of bytes from the UTF-8 string that were consumed.
+ *
+ * Side effects:
+ *     None.
+ *
+ *---------------------------------------------------------------------------
+ */
+#ifndef CHARSET_EBCDIC
+#define ASC(ch) (ch)
+#else /*CHARSET_EBCDIC*/
+#define ASC(ch) os_toascii[(unsigned char) (ch)]
+#endif /*CHARSET_EBCDIC*/
+
+#define Tcl_UniChar int
+#define TCL_UTF_MAX 3
+static int
+gdTcl_UtfToUniChar(char *str, Tcl_UniChar *chPtr)
+/* str is the UTF8 next character pointer */
+/* chPtr is the int for the result */
+{
+    int byte;
+    
+       /* HTML4.0 entities in decimal form, e.g. &#197; */
+    byte = *((unsigned char *) str);
+       if (byte == '&') {
+               int i, n=0;
+
+               byte = *((unsigned char *) (str+1));
+               if (byte == '#') {
+                       for (i = 2; i < 8; i++) {
+                               byte = *((unsigned char *) (str+i));
+                               if (byte >= '0' && byte <= '9') {
+                                       n = (n * 10) + (byte - '0');
+                               } 
+                               else
+                                       break;
+                       }
+                       if (byte == ';') {
+                               *chPtr = (Tcl_UniChar) n;
+                               return ++i;
+                       }
+               }
+       }
+        
+    /*
+     * Unroll 1 to 3 byte UTF-8 sequences, use loop to handle longer ones.
+     */
+
+    byte = ASC(*((unsigned char *) str));
+    if (byte < 0xC0) {
+       /*
+        * Handles properly formed UTF-8 characters between 0x01 and 0x7F.
+        * Also treats \0 and naked trail bytes 0x80 to 0xBF as valid
+        * characters representing themselves.
+        */
+
+       *chPtr = (Tcl_UniChar) byte;
+       return 1;
+    } else if (byte < 0xE0) {
+       if ((ASC(str[1]) & 0xC0) == 0x80) {
+           /*
+            * Two-byte-character lead-byte followed by a trail-byte.
+            */
+            
+           *chPtr = (Tcl_UniChar) (((byte & 0x1F) << 6) | (ASC(str[1]) & 0x3F));
+           return 2;
+       }
+       /*
+        * A two-byte-character lead-byte not followed by trail-byte
+        * represents itself.
+        */
+        
+       *chPtr = (Tcl_UniChar) byte;
+       return 1;
+    } else if (byte < 0xF0) {
+       if (((ASC(str[1]) & 0xC0) == 0x80) && ((ASC(str[2]) & 0xC0) == 0x80)) {
+           /*
+            * Three-byte-character lead byte followed by two trail bytes.
+            */
+
+           *chPtr = (Tcl_UniChar) (((byte & 0x0F) << 12) 
+                   | ((ASC(str[1]) & 0x3F) << 6) | (ASC(str[2]) & 0x3F));
+           return 3;
+       }
+       /*
+        * A three-byte-character lead-byte not followed by two trail-bytes
+        * represents itself.
+        */
+
+       *chPtr = (Tcl_UniChar) byte;
+       return 1;
+    }
+#if TCL_UTF_MAX > 3
+    else {
+       int ch, total, trail;
+
+       total = totalBytes[byte];
+       trail = total - 1;
+       if (trail > 0) {
+           ch = byte & (0x3F >> trail);
+           do {
+               str++;
+               if ((ASC(*str) & 0xC0) != 0x80) {
+                   *chPtr = byte;
+                   return 1;
+               }
+               ch <<= 6;
+               ch |= (ASC(*str) & 0x3F);
+               trail--;
+           } while (trail > 0);
+           *chPtr = ch;
+           return total;
+       }
+    }
+#endif
+
+    *chPtr = (Tcl_UniChar) byte;
+    return 1;
+}
+
+/********************************************************************/
+/* font cache functions                                             */
+
+static int
+fontTest ( void *element, void *key )
+{
+       font_t *a=(font_t *)element;
+       fontkey_t *b=(fontkey_t *)key;
+
+       return ( strcmp(a->fontname, b->fontname) == 0
+                       &&      a->ptsize == b->ptsize
+                       &&  a->angle == b->angle);
+}
+
+static void *
+fontFetch ( char **error, void *key )
+{
+       TT_Error                err;
+       font_t                  *a;
+       fontkey_t               *b=(fontkey_t *)key;
+       int                             i, n;
+       short                   platform, encoding;
+
+       a = (font_t *)malloc(sizeof(font_t));
+       a->fontname = (char *)malloc(strlen(b->fontname) + 1);
+       strcpy(a->fontname,b->fontname);
+       a->ptsize = b->ptsize;
+       a->angle = b->angle;
+       a->sin_a = sin(a->angle);
+       a->cos_a = cos(a->angle);
+       a->engine = b->engine;
+       if ((err = TT_Open_Face(*b->engine, a->fontname, &a->face))) {
+               if (err == TT_Err_Could_Not_Open_File) {
+                       *error = "Could not find/open font";
+               }
+               else {
+                       *error = "Could not read font";
+               }
+               return NULL;
+       }
+       /* get face properties and allocate preload arrays */
+       TT_Get_Face_Properties(a->face, &a->properties);
+
+       /* create instance */
+       if (TT_New_Instance(a->face, &a->instance)) {
+               *error = "Could not create face instance";
+               return NULL;
+       }
+       
+       if (TT_Set_Instance_Resolutions(a->instance, RESOLUTION, RESOLUTION)) {
+               *error = "Could not set device resolutions";
+               return NULL;
+       }
+
+       if (TT_Set_Instance_CharSize(a->instance, (TT_F26Dot6)(a->ptsize*64))) {
+               *error = "Could not set character size";
+               return NULL;
+       }
+
+       TT_Get_Instance_Metrics(a->instance, &a->imetrics);
+       
+       /* First, look for a Unicode charmap */
+       n = TT_Get_CharMap_Count(a->face);
+
+       for (i = 0; i < n; i++) {
+               TT_Get_CharMap_ID(a->face, i, &platform, &encoding);
+               if ((platform == 3 && encoding == 1)  ||
+                   (platform == 2 && encoding == 1)  ||
+                   (platform == 0)) {
+                       TT_Get_CharMap(a->face, i, &a->char_map);
+                       i = n+1;
+               }
+       }
+
+       if (i == n) {
+               *error = "Sorry, but this font doesn't contain any Unicode mapping table";
+               return NULL;
+       }
+
+       a->matrix.xx = (TT_Fixed) (a->cos_a * (1<<16));
+       a->matrix.yx = (TT_Fixed) (a->sin_a * (1<<16));
+       a->matrix.xy = - a->matrix.yx;
+       a->matrix.yy = a->matrix.xx;
+
+       a->glyphCache = gdCacheCreate( GLYPHCACHESIZE,
+                                       glyphTest, glyphFetch, glyphRelease);
+
+       return (void *)a;
+}
+
+static void
+fontRelease( void *element )
+{
+       font_t *a=(font_t *)element;
+
+       gdCacheDelete(a->glyphCache);
+       TT_Done_Instance(a->instance);
+       TT_Close_Face(a->face);
+       free(a->fontname);
+       free( (char *)element );
+}
+
+/********************************************************************/
+/* glyph cache functions                                            */
+
+static int
+glyphTest ( void *element, void *key )
+{
+       glyph_t *a=(glyph_t *)element;
+       glyphkey_t *b=(glyphkey_t *)key;
+
+       return (a->character == b->character
+               && a->hinting == b->hinting
+               && a->gray_render == b->gray_render);
+}
+
+static void *
+glyphFetch ( char **error, void *key )
+{
+       glyph_t                         *a;
+       glyphkey_t                      *b=(glyphkey_t *)key;
+       short                           glyph_code;
+       int                                     flags, err;
+       int                                     crect[8], xmin, xmax, ymin, ymax;
+       double                          cos_a, sin_a;
+
+       a = (glyph_t *)malloc(sizeof(glyph_t));
+       a->character = b->character;
+       a->hinting = b->hinting;
+       a->gray_render = b->gray_render;
+       a->oldx = a->oldy = 0;
+
+       /* create glyph container */
+       if ((TT_New_Glyph(b->font->face, &a->glyph))) {
+               *error = "Could not create glyph container";
+               return NULL;
+       }
+
+       flags = TTLOAD_SCALE_GLYPH;
+       if (a->hinting && b->font->angle == 0.0) {
+               flags |= TTLOAD_HINT_GLYPH;
+       }
+       glyph_code = TT_Char_Index(b->font->char_map, a->character);
+       if ((err=TT_Load_Glyph(b->font->instance, a->glyph, glyph_code, flags))) {
+               *error = "TT_Load_Glyph problem";
+               return NULL;
+       }
+
+       TT_Get_Glyph_Metrics(a->glyph, &a->metrics);
+       if (b->font->angle != 0.0) {
+               TT_Get_Glyph_Outline(a->glyph, &a->outline);
+               TT_Transform_Outline(&a->outline, &b->font->matrix);
+       }
+
+       /* calculate bitmap size */
+       xmin = a->metrics.bbox.xMin -64;
+       ymin = a->metrics.bbox.yMin -64;
+       xmax = a->metrics.bbox.xMax +64;
+       ymax = a->metrics.bbox.yMax +64;
+
+       cos_a = b->font->cos_a;
+       sin_a = b->font->sin_a;
+       crect[0] = (int)(xmin * cos_a - ymin * sin_a);
+       crect[1] = (int)(xmin * sin_a + ymin * cos_a);
+       crect[2] = (int)(xmax * cos_a - ymin * sin_a);
+       crect[3] = (int)(xmax * sin_a + ymin * cos_a);
+       crect[4] = (int)(xmax * cos_a - ymax * sin_a);
+       crect[5] = (int)(xmax * sin_a + ymax * cos_a);
+       crect[6] = (int)(xmin * cos_a - ymax * sin_a);
+       crect[7] = (int)(xmin * sin_a + ymax * cos_a);
+       a->xmin = MIN(MIN(crect[0],crect[2]),MIN(crect[4],crect[6]));
+       a->xmax = MAX(MAX(crect[0],crect[2]),MAX(crect[4],crect[6]));
+       a->ymin = MIN(MIN(crect[1],crect[3]),MIN(crect[5],crect[7]));
+       a->ymax = MAX(MAX(crect[1],crect[3]),MAX(crect[5],crect[7]));
+
+       /* allocate bitmap large enough for character */
+       a->Bit.rows = (a->ymax - a->ymin + 32 + 64) / 64;
+       a->Bit.width = (a->xmax - a->xmin + 32 + 64) / 64;
+       a->Bit.flow = TT_Flow_Up;
+       if (a->gray_render) {
+               a->Bit.cols = a->Bit.width;               /* 1 byte per pixel */
+       }
+       else {
+               a->Bit.cols = (a->Bit.width + 7) / 8;     /* 1 bit per pixel */
+       }
+       a->Bit.cols = (a->Bit.cols + 3) & ~3;         /* pad to 32 bits */
+       a->Bit.size = a->Bit.rows * a->Bit.cols;      /* # of bytes in buffer */
+       a->Bit.bitmap = NULL;
+
+       a->bitmapCache = gdCacheCreate( BITMAPCACHESIZE,
+                                       bitmapTest, bitmapFetch, bitmapRelease);
+
+       return (void *)a;
+}
+
+static void
+glyphRelease( void *element )
+{
+       glyph_t *a=(glyph_t *)element;
+
+       gdCacheDelete(a->bitmapCache);
+       TT_Done_Glyph( a->glyph );
+       free( (char *)element );
+}
+
+/********************************************************************/
+/* bitmap cache functions                                            */
+
+static int
+bitmapTest ( void *element, void *key )
+{
+       bitmap_t *a=(bitmap_t *)element;
+       bitmapkey_t *b=(bitmapkey_t *)key;
+
+       if (a->xoffset == b->xoffset && a->yoffset == b->yoffset) {
+               b->glyph->Bit.bitmap = a->bitmap;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+static void *
+bitmapFetch ( char **error, void *key )
+{
+       bitmap_t                        *a;
+       bitmapkey_t                     *b=(bitmapkey_t *)key;
+
+       a = (bitmap_t *)malloc(sizeof(bitmap_t));
+       a->xoffset = b->xoffset;
+       a->yoffset = b->yoffset;
+
+       b->glyph->Bit.bitmap = a->bitmap = (char *)malloc(b->glyph->Bit.size);
+       memset(a->bitmap, 0, b->glyph->Bit.size);
+       /* render glyph */
+       if (b->glyph->gray_render) {
+               TT_Get_Glyph_Pixmap(b->glyph->glyph, &b->glyph->Bit,
+                       a->xoffset, a->yoffset);
+       }
+       else {
+               TT_Get_Glyph_Bitmap(b->glyph->glyph, &b->glyph->Bit,
+                       a->xoffset, a->yoffset);
+       }
+       return (void *)a;
+}
+
+static void
+bitmapRelease( void *element )
+{
+       bitmap_t *a=(bitmap_t *)element;
+
+       free( a->bitmap );
+       free( (char *)element );
+}
+
+/********************************************************************/
+/* tweencolor cache functions                                            */
+
+static int
+tweenColorTest (void *element, void *key)
+{ 
+    tweencolor_t *a=(tweencolor_t *)element;
+    tweencolorkey_t *b=(tweencolorkey_t *)key;
+    
+    return (a->pixel == b->pixel    
+         && a->bgcolor == b->bgcolor
+         && a->fgcolor == b->fgcolor
+         && a->im == b->im);
+} 
+
+static void *
+tweenColorFetch (char **error, void *key)
+{
+    tweencolor_t *a;
+    tweencolorkey_t *b=(tweencolorkey_t *)key;
+       int pixel, npixel, bg, fg;
+       gdImagePtr im;
+   
+    a = (tweencolor_t *)malloc(sizeof(tweencolor_t));
+       pixel = a->pixel = b->pixel;
+       bg = a->bgcolor = b->bgcolor;
+       fg = a->fgcolor = b->fgcolor;
+       im = b->im;
+
+       /* if fg is specified by a negative color idx, then don't antialias */
+       if (fg <0) {
+               a->tweencolor = -fg;
+       } else {
+               npixel = NUMCOLORS - pixel;
+               a->tweencolor = gdImageColorResolve(im,
+                       (pixel * im->red  [fg] + npixel * im->red  [bg]) / NUMCOLORS,
+                       (pixel * im->green[fg] + npixel * im->green[bg]) / NUMCOLORS,
+                       (pixel * im->blue [fg] + npixel * im->blue [bg]) / NUMCOLORS);
+       }
+    *error = NULL;
+    return (void *)a;
+}   
+        
+static void
+tweenColorRelease(void *element)
+{   
+    free((char *)element);
+}   
+
+/********************************************************************/
+/* gdttfchar -  render one character onto a gd image                */
+
+static int OneTime=0;
+static gdCache_head_t   *tweenColorCache;
+
+char *
+gdttfchar(gdImage *im, int fg, font_t *font,
+       int x, int y,                                   /* string start pos in pixels */
+       TT_F26Dot6 x1,  TT_F26Dot6 y1,  /* char start offset (*64) from x,y */
+       TT_F26Dot6 *advance,
+       TT_BBox **bbox, 
+       char **next)
+{
+    int pc, ch, len;
+       int row, col;
+       int x2, y2;     /* char start pos in pixels */ 
+       int x3, y3;     /* current pixel pos */
+       unsigned char *pixel;
+
+    glyph_t *glyph;
+    glyphkey_t glyphkey;
+    bitmapkey_t bitmapkey;
+       tweencolor_t *tweencolor;
+       tweencolorkey_t tweencolorkey;
+
+       /****** set up tweenColorCache on first call ************/
+       if (! OneTime) {
+               tweenColorCache = gdCacheCreate(TWEENCOLORCACHESIZE,
+                       tweenColorTest, tweenColorFetch, tweenColorRelease);
+               OneTime++;
+       }
+       /**************/
+
+       len = gdTcl_UtfToUniChar(*next, &ch);
+       *next += len;
+
+       glyphkey.character = ch;
+       glyphkey.hinting = 1;
+       /* if fg is specified by a negative color idx, then don't antialias */
+       glyphkey.gray_render = ((font->ptsize < MINANTIALIASPTSIZE) || (fg <0))?FALSE:TRUE;
+    glyphkey.font = font;
+    glyph = (glyph_t *)gdCacheGet(font->glyphCache, &glyphkey);
+    if (! glyph)
+               return font->glyphCache->error;
+
+       *bbox = &glyph->metrics.bbox;
+       *advance = glyph->metrics.advance;
+
+       /* if null *im, or invalid color,  then assume user just wants brect */
+       if (!im || fg > 255 || fg < -255)
+               return (char *)NULL;
+
+       /* render (via cache) a bitmap for the current fractional offset */
+       bitmapkey.xoffset = ((x1+32) & 63) - 32 - ((glyph->xmin+32) & -64);
+       bitmapkey.yoffset = ((y1+32) & 63) - 32 - ((glyph->ymin+32) & -64);
+       bitmapkey.glyph = glyph;
+       gdCacheGet(glyph->bitmapCache, &bitmapkey);
+
+       /* copy to gif, mapping colors */
+      x2 = x + (((glyph->xmin+32) & -64) + ((x1+32) & -64)) / 64;
+      y2 = y - (((glyph->ymin+32) & -64) + ((y1+32) & -64)) / 64;
+       tweencolorkey.fgcolor = fg;
+       tweencolorkey.im = im;
+       for (row = 0; row < glyph->Bit.rows; row++) {
+               if (glyph->gray_render)
+                       pc = row * glyph->Bit.cols;
+               else
+                       pc = row * glyph->Bit.cols * 8;
+               y3 = y2 - row;
+               if (y3 >= im->sy || y3 < 0) continue;
+               for (col = 0; col < glyph->Bit.width; col++, pc++) {
+                       if (glyph->gray_render) {
+                               tweencolorkey.pixel = 
+                                       *((unsigned char *)(glyph->Bit.bitmap) + pc);
+                       } else {
+                               tweencolorkey.pixel = 
+                                       (((*((unsigned char *)(glyph->Bit.bitmap) + pc/8))
+                                               <<(pc%8))&128)?4:0;
+                       }
+                       /* if not background */
+                       if (tweencolorkey.pixel > 0) {
+                               x3 = x2 + col;
+                               if (x3 >= im->sx || x3 < 0) continue;
+#if HAVE_LIBGD13
+                               pixel = &im->pixels[y3][x3];
+#else
+                               pixel = &im->pixels[x3][y3];
+#endif
+                               tweencolorkey.bgcolor = *pixel;
+                               tweencolor = (tweencolor_t *)gdCacheGet(
+                                       tweenColorCache, &tweencolorkey);
+                               *pixel = tweencolor->tweencolor;
+                       }
+               }
+       }
+       return (char *)NULL;
+}
+
+/********************************************************************/
+/* gdttf -  render a utf8 string onto a gd image                                       */
+
+char *
+gdttf(gdImage *im, int *brect, int fg, char *fontname,
+       double ptsize, double angle, int x, int y, char *string)
+{
+       TT_F26Dot6 ur_x=0, ur_y=0, ll_x=0, ll_y=0;
+       TT_F26Dot6 advance_x, advance_y, advance, x1, y1;
+       TT_BBox *bbox;
+       double sin_a, cos_a;
+    int i=0, ch;
+       font_t *font;
+       fontkey_t fontkey;
+       char *error, *next;
+
+       /****** initialize font engine on first call ************/
+    static gdCache_head_t      *fontCache;
+       static TT_Engine        engine;
+
+       if (! fontCache) {
+               if (TT_Init_FreeType(&engine)) {
+                       return "Failure to initialize font engine";
+               }
+               fontCache = gdCacheCreate( FONTCACHESIZE,
+                       fontTest, fontFetch, fontRelease);
+       }
+       /**************/
+
+       /* get the font (via font cache) */
+       fontkey.fontname = fontname;
+       fontkey.ptsize = ptsize;
+       fontkey.angle = angle;
+       fontkey.engine = &engine;
+       font = (font_t *)gdCacheGet(fontCache, &fontkey);
+       if (! font) {
+               return fontCache->error;
+       }
+       sin_a = font->sin_a;
+       cos_a = font->cos_a;
+       advance_x = advance_y = 0;
+
+       next=string;
+       while (*next) {   
+               ch = *next;
+
+               /* carriage returns */
+               if (ch == '\r') {
+                       advance_x = 0;
+                       next++;
+                       continue;
+               }
+               /* newlines */
+               if (ch == '\n') {
+                      advance_y -= (TT_F26Dot6)(font->imetrics.y_ppem * LINESPACE * 64);
+                      advance_y = (advance_y-32) & -64; /* round to next pixel row */
+                       next++;
+                       continue;
+               }
+
+               x1 = (TT_F26Dot6)(advance_x * cos_a - advance_y * sin_a);
+               y1 = (TT_F26Dot6)(advance_x * sin_a + advance_y * cos_a);
+
+               if ((error=gdttfchar(im, fg, font, x, y, x1, y1, &advance, &bbox, &next)))
+                       return error;
+
+               if (! i++) { /* if first character, init BB corner values */
+                       ll_x = bbox->xMin;
+                       ll_y = bbox->yMin;
+                       ur_x = bbox->xMax;
+                       ur_y = bbox->yMax;
+               }
+               else {
+                       if (! advance_x) ll_x = MIN(bbox->xMin, ll_x);
+                       ll_y = MIN(advance_y + bbox->yMin, ll_y);
+                       ur_x = MAX(advance_x + bbox->xMax, ur_x);
+                       if (! advance_y) ur_y = MAX(bbox->yMax, ur_y);
+               }
+               advance_x += advance;
+       }
+
+       /* rotate bounding rectangle */
+       brect[0] = (int)(ll_x * cos_a - ll_y * sin_a);
+       brect[1] = (int)(ll_x * sin_a + ll_y * cos_a);
+       brect[2] = (int)(ur_x * cos_a - ll_y * sin_a);
+       brect[3] = (int)(ur_x * sin_a + ll_y * cos_a);
+       brect[4] = (int)(ur_x * cos_a - ur_y * sin_a);
+       brect[5] = (int)(ur_x * sin_a + ur_y * cos_a);
+       brect[6] = (int)(ll_x * cos_a - ur_y * sin_a);
+       brect[7] = (int)(ll_x * sin_a + ur_y * cos_a);
+
+       /* scale, round and offset brect */
+       i = 0;
+       while (i<8) {
+               brect[i] = x + (brect[i] + 32) / 64;
+               i++;
+               brect[i] = y - (brect[i] + 32) / 64;
+               i++;
+       }
+
+    return (char *)NULL;
+}
+   
+#endif /* HAVE_LIBTTF */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/ext/gd/gdttf.h b/ext/gd/gdttf.h
new file mode 100644 (file)
index 0000000..86a5eca
--- /dev/null
@@ -0,0 +1,16 @@
+/* $Id$ */
+
+#ifdef _OSD_POSIX
+#ifndef APACHE
+#error On this EBCDIC platform, PHP3 is only supported as an Apache module.
+#else /*APACHE*/
+#ifndef CHARSET_EBCDIC
+#define CHARSET_EBCDIC /* this machine uses EBCDIC, not ASCII! */
+#endif
+#include "ebcdic.h"
+#endif /*APACHE*/
+#endif /*_OSD_POSIX*/
+
+char * gdttf(gdImage *im, int *brect, int fg, char *fontname,
+    double ptsize, double angle, int x, int y, char *string);
+