#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "gd.h"
#include "gdhelpers.h"
#ifndef WIN32
#include <unistd.h>
#else
#include <io.h>
#define R_OK 04
#endif
#define NUMCOLORS 8
char *
gdImageStringTTF (gdImage * im, int *brect, int fg, char *fontlist,
double ptsize, double angle, int x, int y, char *string)
{
return gdImageStringFT (im, brect, fg, fontlist, ptsize,
angle, x, y, string);
}
#ifndef HAVE_LIBFREETYPE
char *
gdImageStringFTEx (gdImage * im, int *brect, int fg, char *fontlist,
double ptsize, double angle, int x, int y, char *string,
gdFTStringExtraPtr strex)
{
return "libgd was not built with FreeType font support\n";
}
char *
gdImageStringFT (gdImage * im, int *brect, int fg, char *fontlist,
double ptsize, double angle, int x, int y, char *string)
{
return "libgd was not built with FreeType font support\n";
}
#else
#include "gdcache.h"
#ifdef HAVE_FT2BUILD_H
#include "ft2build.h"
#include FT_FREETYPE_H
#include FT_GLYPH_H
#else
#include "freetype/freetype.h"
#include "freetype/ftglyph.h"
#endif
#define FONTCACHESIZE 6
#define TWEENCOLORCACHESIZE 32
#define LINESPACE 1.05
#define LISTSEPARATOR ";"
#ifndef DEFAULT_FONTPATH
#if defined(__APPLE__) || (defined(__MWERKS__) && defined(macintosh))
#define DEFAULT_FONTPATH "/usr/share/fonts/truetype:/System/Library/Fonts:/Library/Fonts"
#else
#define DEFAULT_FONTPATH "/usr/share/fonts/truetype"
#endif
#endif
#ifndef PATHSEPARATOR
#define PATHSEPARATOR ":"
#endif
#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 *fontlist;
FT_Library *library;
FT_Face face;
FT_Bool have_char_map_unicode, have_char_map_big5, have_char_map_sjis,
have_char_map_apple_roman;
gdCache_head_t *glyphCache;
}
font_t;
typedef struct
{
char *fontlist;
FT_Library *library;
}
fontkey_t;
typedef struct
{
int pixel;
int bgcolor;
int fgcolor;
gdImagePtr im;
int tweencolor;
}
tweencolor_t;
typedef struct
{
int pixel;
int bgcolor;
int fgcolor;
gdImagePtr im;
}
tweencolorkey_t;
#ifdef JISX0208
#include "jisx0208.h"
#endif
#define Tcl_UniChar int
#define TCL_UTF_MAX 3
static int
gdTcl_UtfToUniChar (char *str, Tcl_UniChar * chPtr)
{
int byte;
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;
}
}
}
byte = *((unsigned char *) str);
#ifdef JISX0208
if (0xA1 <= byte && byte <= 0xFE)
{
int ku, ten;
ku = (byte & 0x7F) - 0x20;
ten = (str[1] & 0x7F) - 0x20;
if ((ku < 1 || ku > 92) || (ten < 1 || ten > 94))
{
*chPtr = (Tcl_UniChar) byte;
return 1;
}
*chPtr = (Tcl_UniChar) UnicodeTbl[ku - 1][ten - 1];
return 2;
}
else
#endif
if (byte < 0xC0)
{
*chPtr = (Tcl_UniChar) byte;
return 1;
}
else if (byte < 0xE0)
{
if ((str[1] & 0xC0) == 0x80)
{
*chPtr = (Tcl_UniChar) (((byte & 0x1F) << 6) | (str[1] & 0x3F));
return 2;
}
*chPtr = (Tcl_UniChar) byte;
return 1;
}
else if (byte < 0xF0)
{
if (((str[1] & 0xC0) == 0x80) && ((str[2] & 0xC0) == 0x80))
{
*chPtr = (Tcl_UniChar) (((byte & 0x0F) << 12)
| ((str[1] & 0x3F) << 6) | (str[2] & 0x3F));
return 3;
}
*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 ((*str & 0xC0) != 0x80)
{
*chPtr = byte;
return 1;
}
ch <<= 6;
ch |= (*str & 0x3F);
trail--;
}
while (trail > 0);
*chPtr = ch;
return total;
}
}
#endif
*chPtr = (Tcl_UniChar) byte;
return 1;
}
static int
fontTest (void *element, void *key)
{
font_t *a = (font_t *) element;
fontkey_t *b = (fontkey_t *) key;
return (strcmp (a->fontlist, b->fontlist) == 0);
}
static void *
fontFetch (char **error, void *key)
{
font_t *a;
fontkey_t *b = (fontkey_t *) key;
int n;
int font_found = 0;
unsigned short platform, encoding;
char *fontsearchpath, *fontlist;
char *fullname = NULL;
char *name, *path, *dir;
char *strtok_ptr;
FT_Error err;
FT_CharMap found = 0;
FT_CharMap charmap;
a = (font_t *) gdMalloc (sizeof (font_t));
a->fontlist = strdup (b->fontlist);
a->library = b->library;
fontsearchpath = getenv ("GDFONTPATH");
if (!fontsearchpath)
fontsearchpath = DEFAULT_FONTPATH;
fontlist = strdup (a->fontlist);
for (name = gd_strtok_r (fontlist, LISTSEPARATOR, &strtok_ptr); name;
name = gd_strtok_r (0, LISTSEPARATOR, &strtok_ptr))
{
path = strdup (fontsearchpath);
fullname = gdRealloc (fullname,
strlen (fontsearchpath) + strlen (name) + 6);
if (strchr (name, '/')
|| (name[0] != 0 && name[1] == ':'
&& (name[2] == '/' || name[2] == '\\')))
{
sprintf (fullname, "%s", name);
if (access (fullname, R_OK) == 0)
{
font_found++;
gdFree(path);
break;
}
}
for (dir = strtok (path, PATHSEPARATOR); dir;
dir = strtok (0, PATHSEPARATOR))
{
sprintf (fullname, "%s/%s.ttf", dir, name);
if (access (fullname, R_OK) == 0)
{
font_found++;
break;
}
sprintf (fullname, "%s/%s.pfa", dir, name);
if (access (fullname, R_OK) == 0)
{
font_found++;
break;
}
sprintf (fullname, "%s/%s.pfb", dir, name);
if (access (fullname, R_OK) == 0)
{
font_found++;
break;
}
sprintf (fullname, "%s/%s.dfont", dir, name);
if (access (fullname, R_OK) == 0)
{
font_found++;
break;
}
}
gdFree (path);
if (font_found)
break;
}
gdFree (fontlist);
if (!font_found)
{
*error = "Could not find/open font";
gdFree (a->fontlist);
gdFree (fullname);
gdFree (a);
return NULL;
}
err = FT_New_Face (*b->library, fullname, 0, &a->face);
if (err)
{
free (a->fontlist);
free (fullname);
gdFree (a);
*error = "Could not read font";
return NULL;
}
gdFree (fullname);
a->have_char_map_unicode = 0;
a->have_char_map_big5 = 0;
a->have_char_map_sjis = 0;
a->have_char_map_apple_roman = 0;
for (n = 0; n < a->face->num_charmaps; n++)
{
charmap = a->face->charmaps[n];
platform = charmap->platform_id;
encoding = charmap->encoding_id;
if ((platform == 3 && encoding == 1)
|| (platform == 3 && encoding == 0)
|| (platform == 2 && encoding == 1)
|| (platform == 0))
{
a->have_char_map_unicode = 1;
found = charmap;
}
else if (platform == 3 && encoding == 4)
{
a->have_char_map_big5 = 1;
found = charmap;
}
else if (platform == 3 && encoding == 2)
{
a->have_char_map_sjis = 1;
found = charmap;
}
else if ((platform == 1 && encoding == 0)
|| (platform == 2 && encoding == 0))
{
a->have_char_map_apple_roman = 1;
found = charmap;
}
}
if (!found)
{
*error = "Unable to find a CharMap that I can handle";
free (a->fontlist);
gdFree (a);
return NULL;
}
a->face->charmap = found;
return (void *) a;
}
static void
fontRelease (void *element)
{
font_t *a = (font_t *) element;
FT_Done_Face (a->face);
gdFree (a->fontlist);
gdFree ((char *) element);
}
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 *) gdMalloc (sizeof (tweencolor_t));
pixel = a->pixel = b->pixel;
bg = a->bgcolor = b->bgcolor;
fg = a->fgcolor = b->fgcolor;
im = b->im;
if (fg < 0)
{
if ((pixel + pixel) >= NUMCOLORS)
a->tweencolor = -fg;
else
a->tweencolor = bg;
}
else
{
npixel = NUMCOLORS - pixel;
if (im->trueColor)
{
a->tweencolor = gdTrueColorAlpha (gdTrueColorGetRed (fg),
gdTrueColorGetGreen (fg),
gdTrueColorGetBlue (fg),
gdAlphaMax -
(gdTrueColorGetAlpha (fg) *
pixel / NUMCOLORS));
}
else
{
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);
}
}
return (void *) a;
}
static void
tweenColorRelease (void *element)
{
gdFree ((char *) element);
}
static char *
gdft_draw_bitmap (gdCache_head_t * tc_cache, gdImage * im, int fg,
FT_Bitmap bitmap, int pen_x, int pen_y)
{
unsigned char *pixel = NULL;
int *tpixel = NULL;
int x, y, row, col, pc, pcr;
tweencolor_t *tc_elem;
tweencolorkey_t tc_key;
tc_key.fgcolor = fg;
tc_key.im = im;
if (im->trueColor)
{
for (row = 0; row < bitmap.rows; row++)
{
pc = row * bitmap.pitch;
pcr = pc;
y = pen_y + row;
if ((y > im->cy2) || (y < im->cy1))
continue;
for (col = 0; col < bitmap.width; col++, pc++)
{
int level;
if (bitmap.pixel_mode == ft_pixel_mode_grays)
{
level = (bitmap.buffer[pc] * gdAlphaMax /
(bitmap.num_grays - 1));
}
else if (bitmap.pixel_mode == ft_pixel_mode_mono)
{
level =
((bitmap.
buffer[(col >> 3) +
pcr]) & (1 << (~col & 0x07))) ?
gdAlphaTransparent : gdAlphaOpaque;
}
else
{
return "Unsupported ft_pixel_mode";
}
if ((fg >= 0) && (im->trueColor))
{
level =
level * (gdAlphaMax -
gdTrueColorGetAlpha (fg)) / gdAlphaMax;
}
level = gdAlphaMax - level;
x = pen_x + col;
if ((x > im->cx2) || (x < im->cx1))
continue;
tpixel = &im->tpixels[y][x];
if (fg < 0)
{
if (level < (gdAlphaMax / 2))
{
*tpixel = -fg;
}
}
else
{
if (im->alphaBlendingFlag)
{
*tpixel =
gdAlphaBlend (*tpixel,
(level << 24) + (fg & 0xFFFFFF));
}
else
{
*tpixel = (level << 24) + (fg & 0xFFFFFF);
}
}
}
}
return (char *) NULL;
}
for (row = 0; row < bitmap.rows; row++)
{
int pcr;
pc = row * bitmap.pitch;
pcr = pc;
if (bitmap.pixel_mode == ft_pixel_mode_mono)
pc *= 8;
y = pen_y + row;
if (y >= im->sy || y < 0)
continue;
for (col = 0; col < bitmap.width; col++, pc++)
{
if (bitmap.pixel_mode == ft_pixel_mode_grays)
{
tc_key.pixel = ((bitmap.buffer[pc] * NUMCOLORS)
+ bitmap.num_grays / 2)
/ (bitmap.num_grays - 1);
}
else if (bitmap.pixel_mode == ft_pixel_mode_mono)
{
tc_key.pixel = ((bitmap.buffer[pc / 8]
<< (pc % 8)) & 128) ? NUMCOLORS : 0;
tc_key.pixel =
((bitmap.
buffer[(col >> 3) +
pcr]) & (1 << (~col & 0x07))) ? NUMCOLORS : 0;
}
else
{
return "Unsupported ft_pixel_mode";
}
if (tc_key.pixel > 0)
{
x = pen_x + col;
if (x >= im->sx || x < 0)
continue;
pixel = &im->pixels[y][x];
if (tc_key.pixel == NUMCOLORS)
{
*pixel = (fg < 0) ? -fg : fg;
}
else
{
tc_key.bgcolor = *pixel;
tc_elem = (tweencolor_t *) gdCacheGet (tc_cache, &tc_key);
*pixel = tc_elem->tweencolor;
}
}
}
}
return (char *) NULL;
}
static int
gdroundupdown (FT_F26Dot6 v1, int updown)
{
return (!updown)
? (v1 < 0 ? ((v1 - 63) >> 6) : v1 >> 6)
: (v1 > 0 ? ((v1 + 63) >> 6) : v1 >> 6);
}
extern int any2eucjp (char *, char *, unsigned int);
static gdCache_head_t *fontCache;
static FT_Library library;
void
gdFreeFontCache ()
{
if (fontCache)
{
gdCacheDelete (fontCache);
FT_Done_FreeType (library);
}
}
char *
gdImageStringFT (gdImage * im, int *brect, int fg, char *fontlist,
double ptsize, double angle, int x, int y, char *string)
{
return gdImageStringFTEx (im, brect, fg, fontlist,
ptsize, angle, x, y, string, 0);
}
char *
gdImageStringFTEx (gdImage * im, int *brect, int fg, char *fontlist,
double ptsize, double angle, int x, int y, char *string,
gdFTStringExtraPtr strex)
{
FT_BBox bbox, glyph_bbox;
FT_Matrix matrix;
FT_Vector pen, delta, penf;
FT_Face face;
FT_Glyph image;
FT_GlyphSlot slot;
FT_Error err;
FT_Bool use_kerning;
FT_UInt glyph_index, previous;
double sin_a = sin (angle);
double cos_a = cos (angle);
int len, i = 0, ch;
int x1 = 0, y1 = 0;
font_t *font;
fontkey_t fontkey;
char *next;
char *tmpstr = 0;
int render = (im && (im->trueColor || (fg <= 255 && fg >= -255)));
FT_BitmapGlyph bm;
int render_mode = FT_LOAD_DEFAULT;
int m, mfound;
double linespace = LINESPACE;
gdCache_head_t *tc_cache;
if (strex)
{
if ((strex->flags & gdFTEX_LINESPACE) == gdFTEX_LINESPACE)
{
linespace = strex->linespacing;
}
}
tc_cache = gdCacheCreate (TWEENCOLORCACHESIZE,
tweenColorTest, tweenColorFetch,
tweenColorRelease);
if (!fontCache)
{
if (FT_Init_FreeType (&library))
{
gdCacheDelete (tc_cache);
return "Failure to initialize font library";
}
fontCache = gdCacheCreate (FONTCACHESIZE,
fontTest, fontFetch, fontRelease);
}
fontkey.fontlist = fontlist;
fontkey.library = &library;
font = (font_t *) gdCacheGet (fontCache, &fontkey);
if (!font)
{
gdCacheDelete (tc_cache);
return fontCache->error;
}
face = font->face;
slot = face->glyph;
if (FT_Set_Char_Size (face, 0, (FT_F26Dot6) (ptsize * 64),
GD_RESOLUTION, GD_RESOLUTION))
{
gdCacheDelete (tc_cache);
return "Could not set character size";
}
matrix.xx = (FT_Fixed) (cos_a * (1 << 16));
matrix.yx = (FT_Fixed) (sin_a * (1 << 16));
matrix.xy = -matrix.yx;
matrix.yy = matrix.xx;
penf.x = penf.y = 0;
pen.x = pen.y = 0;
bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0;
use_kerning = FT_HAS_KERNING (face);
previous = 0;
if (fg < 0)
{
render_mode |= FT_LOAD_MONOCHROME;
}
m = gdFTEX_Unicode;
if (strex && (strex->flags & gdFTEX_CHARMAP))
{
m = strex->charmap;
}
mfound = 0;
for (i = 0; (i < 3); i++)
{
switch (m)
{
case gdFTEX_Unicode:
if (font->have_char_map_unicode)
{
mfound = 1;
}
break;
case gdFTEX_Shift_JIS:
if (font->have_char_map_sjis)
{
mfound = 1;
}
break;
case gdFTEX_Big5:
{
mfound = 1;
}
break;
}
if (mfound)
{
break;
}
m++;
m %= 3;
}
if (!mfound)
{
return "No character set found";
}
#ifndef JISX0208
if (font->have_char_map_sjis)
{
#endif
if ((tmpstr = (char *) gdMalloc (BUFSIZ)))
{
any2eucjp (tmpstr, string, BUFSIZ);
next = tmpstr;
}
else
{
next = string;
}
#ifndef JISX0208
}
else
{
next = string;
}
#endif
while (*next)
{
ch = *next;
if (ch == '\r')
{
penf.x = 0;
x1 = (penf.x * cos_a - penf.y * sin_a + 32) / 64;
y1 = (penf.x * sin_a + penf.y * cos_a + 32) / 64;
pen.x = pen.y = 0;
previous = 0;
next++;
continue;
}
if (ch == '\n')
{
penf.x = 0;
penf.y -= face->size->metrics.height * linespace;
penf.y = (penf.y - 32) & -64;
x1 = (penf.x * cos_a - penf.y * sin_a + 32) / 64;
y1 = (penf.x * sin_a + penf.y * cos_a + 32) / 64;
pen.x = pen.y = 0;
previous = 0;
next++;
continue;
}
switch (m)
{
case gdFTEX_Unicode:
if (font->have_char_map_unicode)
{
len = gdTcl_UtfToUniChar (next, &ch);
next += len;
}
break;
case gdFTEX_Shift_JIS:
if (font->have_char_map_sjis)
{
unsigned char c;
int jiscode;
c = *next;
if (0xA1 <= c && c <= 0xFE)
{
next++;
jiscode = 0x100 * (c & 0x7F) + ((*next) & 0x7F);
ch = (jiscode >> 8) & 0xFF;
jiscode &= 0xFF;
if (ch & 1)
jiscode += 0x40 - 0x21;
else
jiscode += 0x9E - 0x21;
if (jiscode >= 0x7F)
jiscode++;
ch = (ch - 0x21) / 2 + 0x81;
if (ch >= 0xA0)
ch += 0x40;
ch = (ch << 8) + jiscode;
}
else
{
ch = c & 0xFF;
}
next++;
}
break;
case gdFTEX_Big5:
{
ch = (*next) & 0xFF;
next++;
if (ch >= 161
&& *next)
{
ch = (ch * 256) + ((*next) & 255);
next++;
}
}
break;
}
FT_Set_Transform (face, &matrix, NULL);
glyph_index = FT_Get_Char_Index (face, ch);
if (use_kerning && previous && glyph_index)
{
FT_Get_Kerning (face, previous, glyph_index,
ft_kerning_default, &delta);
pen.x += delta.x;
penf.x += delta.x;
}
err = FT_Load_Glyph (face, glyph_index, render_mode);
if (err)
{
gdCacheDelete (tc_cache);
return "Problem loading glyph";
}
FT_Get_Glyph (slot, &image);
if (brect)
{
FT_Glyph_Get_CBox (image, ft_glyph_bbox_gridfit, &glyph_bbox);
glyph_bbox.xMin += penf.x;
glyph_bbox.yMin += penf.y;
glyph_bbox.xMax += penf.x;
glyph_bbox.yMax += penf.y;
if (ch == ' ')
glyph_bbox.xMax += slot->metrics.horiAdvance;
if (!i)
{
bbox.xMin = glyph_bbox.xMin;
bbox.yMin = glyph_bbox.yMin;
bbox.xMax = glyph_bbox.xMax;
bbox.yMax = glyph_bbox.yMax;
}
else
{
if (bbox.xMin > glyph_bbox.xMin)
bbox.xMin = glyph_bbox.xMin;
if (bbox.yMin > glyph_bbox.yMin)
bbox.yMin = glyph_bbox.yMin;
if (bbox.xMax < glyph_bbox.xMax)
bbox.xMax = glyph_bbox.xMax;
if (bbox.yMax < glyph_bbox.yMax)
bbox.yMax = glyph_bbox.yMax;
}
i++;
}
if (render)
{
if (image->format != ft_glyph_format_bitmap)
{
err = FT_Glyph_To_Bitmap (&image, ft_render_mode_normal, 0, 1);
if (err)
{
gdCacheDelete (tc_cache);
return "Problem rendering glyph";
}
}
bm = (FT_BitmapGlyph) image;
gdft_draw_bitmap (tc_cache, im, fg, bm->bitmap,
x + x1 + ((pen.x + 31) >> 6) + bm->left,
y - y1 + ((pen.y + 31) >> 6) - bm->top);
}
previous = glyph_index;
pen.x += image->advance.x >> 10;
pen.y -= image->advance.y >> 10;
penf.x += slot->metrics.horiAdvance;
FT_Done_Glyph (image);
}
if (brect)
{
double d1 = sin (angle + 0.78539816339744830962);
double d2 = sin (angle - 0.78539816339744830962);
brect[0] = (int) (bbox.xMin * cos_a - bbox.yMin * sin_a);
brect[1] = (int) (bbox.xMin * sin_a + bbox.yMin * cos_a);
brect[2] = (int) (bbox.xMax * cos_a - bbox.yMin * sin_a);
brect[3] = (int) (bbox.xMax * sin_a + bbox.yMin * cos_a);
brect[4] = (int) (bbox.xMax * cos_a - bbox.yMax * sin_a);
brect[5] = (int) (bbox.xMax * sin_a + bbox.yMax * cos_a);
brect[6] = (int) (bbox.xMin * cos_a - bbox.yMax * sin_a);
brect[7] = (int) (bbox.xMin * sin_a + bbox.yMax * cos_a);
brect[0] = x + gdroundupdown (brect[0], d2 > 0);
brect[1] = y - gdroundupdown (brect[1], d1 < 0);
brect[2] = x + gdroundupdown (brect[2], d1 > 0);
brect[3] = y - gdroundupdown (brect[3], d2 > 0);
brect[4] = x + gdroundupdown (brect[4], d2 < 0);
brect[5] = y - gdroundupdown (brect[5], d1 > 0);
brect[6] = x + gdroundupdown (brect[6], d1 < 0);
brect[7] = y - gdroundupdown (brect[7], d2 < 0);
}
if (tmpstr)
gdFree (tmpstr);
gdCacheDelete (tc_cache);
return (char *) NULL;
}
#endif