diff options
-rw-r--r-- | ext/gd/gd.c | 1786 | ||||
-rw-r--r-- | ext/gd/gdcache.c | 201 | ||||
-rw-r--r-- | ext/gd/gdcache.h | 87 | ||||
-rw-r--r-- | ext/gd/gdttf.c | 862 | ||||
-rw-r--r-- | ext/gd/gdttf.h | 16 |
5 files changed, 2952 insertions, 0 deletions
diff --git a/ext/gd/gd.c b/ext/gd/gd.c new file mode 100644 index 0000000000..5edcc9ade1 --- /dev/null +++ b/ext/gd/gd.c @@ -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 index 0000000000..3fd82dea70 --- /dev/null +++ b/ext/gd/gdcache.c @@ -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 index 0000000000..cdfbf71f95 --- /dev/null +++ b/ext/gd/gdcache.h @@ -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 index 0000000000..39e93ed339 --- /dev/null +++ b/ext/gd/gdttf.c @@ -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. Å */ + 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 index 0000000000..86a5eca1fe --- /dev/null +++ b/ext/gd/gdttf.h @@ -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); + |