/* SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts Copyright (C) 1997-2004 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Sam Lantinga slouken@libsdl.org */ /* $Id: SDL_ttf.c 2304 2006-05-01 09:26:07Z slouken $ */ #include #include #include #include #ifdef HAVE_ALLOCA_H #include #endif #ifdef HAVE_ALLOCA #define ALLOCA(n) ((void*)alloca(n)) #define FREEA(p) #else #define ALLOCA(n) malloc(n) #define FREEA(p) free(p) #endif #include #include FT_FREETYPE_H #include FT_OUTLINE_H #include FT_TRUETYPE_IDS_H /* #include #include #include */ #include #ifndef FT_OPEN_STREAM #define FT_OPEN_STREAM ft_open_stream #endif #include "SDL.h" #include "SDL_endian.h" #include "SDL_ttf.h" /* FIXME: Right now we assume the gray-scale renderer Freetype is using supports 256 shades of gray, but we should instead key off of num_grays in the result FT_Bitmap after the FT_Render_Glyph() call. */ #define NUM_GRAYS 256 /* Handy routines for converting from fixed point */ #define FT_FLOOR(X) ((X & -64) / 64) #define FT_CEIL(X) (((X + 63) & -64) / 64) #define CACHED_METRICS 0x10 #define CACHED_BITMAP 0x01 #define CACHED_PIXMAP 0x02 /* Cached glyph information */ typedef struct cached_glyph { int stored; FT_UInt index; FT_Bitmap bitmap; FT_Bitmap pixmap; int minx; int maxx; int miny; int maxy; int yoffset; int advance; Uint16 cached; } c_glyph; /* The structure used to hold internal font information */ struct _TTF_Font { /* Freetype2 maintains all sorts of useful info itself */ FT_Face face; /* We'll cache these ourselves */ int height; int ascent; int descent; int lineskip; /* The font style */ int style; /* Extra width in glyph bounds for text styles */ int glyph_overhang; float glyph_italics; /* Information in the font for underlining */ int underline_offset; int underline_height; /* Cache for style-transformed glyphs */ c_glyph *current; c_glyph cache[256]; c_glyph scratch; /* We are responsible for closing the font stream */ SDL_RWops *src; int freesrc; FT_Open_Args args; /* For non-scalable formats, we must remember which font index size */ int font_size_family; }; /* The FreeType font engine/library */ static FT_Library library; static int TTF_initialized = 0; static int TTF_byteswapped = 0; /* UNICODE string utilities */ static __inline__ int UNICODE_strlen(const Uint16 *text) { int size = 0; while ( *text++ ) { ++size; } return size; } static __inline__ void UNICODE_strcpy(Uint16 *dst, const Uint16 *src, int swap) { if ( swap ) { while ( *src ) { *dst = SDL_Swap16(*src); ++src; ++dst; } *dst = '\0'; } else { while ( *src ) { *dst = *src; ++src; ++dst; } *dst = '\0'; } } /* rcg06192001 get linked library's version. */ const SDL_version *TTF_Linked_Version(void) { static SDL_version linked_version; SDL_TTF_VERSION(&linked_version); return(&linked_version); } /* This function tells the library whether UNICODE text is generally byteswapped. A UNICODE BOM character at the beginning of a string will override this setting for that string. */ void TTF_ByteSwappedUNICODE(int swapped) { TTF_byteswapped = swapped; } static void TTF_SetFTError(const char *msg, FT_Error error) { #ifdef USE_FREETYPE_ERRORS #undef FTERRORS_H #define FT_ERRORDEF( e, v, s ) { e, s }, static const struct { int err_code; const char* err_msg; } ft_errors[] = { #include }; int i; const char *err_msg; char buffer[1024]; err_msg = NULL; for ( i=0; i<((sizeof ft_errors)/(sizeof ft_errors[0])); ++i ) { if ( error == ft_errors[i].err_code ) { err_msg = ft_errors[i].err_msg; break; } } if ( ! err_msg ) { err_msg = "unknown FreeType error"; } sprintf(buffer, "%s: %s", msg, err_msg); TTF_SetError(buffer); #else TTF_SetError(msg); #endif /* USE_FREETYPE_ERRORS */ } int TTF_Init( void ) { int status = 0; if ( ! TTF_initialized ) { FT_Error error = FT_Init_FreeType( &library ); if ( error ) { TTF_SetFTError("Couldn't init FreeType engine", error); status = -1; } } if ( status == 0 ) { ++TTF_initialized; } return status; } static unsigned long RWread( FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count ) { SDL_RWops *src; src = (SDL_RWops *)stream->descriptor.pointer; SDL_RWseek( src, (int)offset, SEEK_SET ); if ( count == 0 ) { return 0; } return SDL_RWread( src, buffer, 1, (int)count ); } TTF_Font* TTF_OpenFontIndexRW( SDL_RWops *src, int freesrc, int ptsize, long index ) { TTF_Font* font; FT_Error error; FT_Face face; FT_Fixed scale; FT_Stream stream; int position; if ( ! TTF_initialized ) { TTF_SetError( "Library not initialized" ); return NULL; } /* Check to make sure we can seek in this stream */ position = SDL_RWtell(src); if ( position < 0 ) { TTF_SetError( "Can't seek in stream" ); return NULL; } font = (TTF_Font*) malloc(sizeof *font); if ( font == NULL ) { TTF_SetError( "Out of memory" ); return NULL; } memset(font, 0, sizeof(*font)); font->src = src; font->freesrc = freesrc; stream = (FT_Stream)malloc(sizeof(*stream)); if ( stream == NULL ) { TTF_SetError( "Out of memory" ); TTF_CloseFont( font ); return NULL; } memset(stream, 0, sizeof(*stream)); stream->memory = library->memory; stream->read = RWread; stream->descriptor.pointer = src; stream->pos = (unsigned long)position; SDL_RWseek(src, 0, SEEK_END); stream->size = (unsigned long)(SDL_RWtell(src) - position); SDL_RWseek(src, position, SEEK_SET); font->args.flags = FT_OPEN_STREAM; font->args.stream = stream; error = FT_Open_Face( library, &font->args, index, &font->face ); if( error ) { TTF_SetFTError( "Couldn't load font file", error ); TTF_CloseFont( font ); return NULL; } face = font->face; /* Make sure that our font face is scalable (global metrics) */ if ( FT_IS_SCALABLE(face) ) { /* Set the character size and use default DPI (72) */ error = FT_Set_Char_Size( font->face, 0, ptsize * 64, 0, 0 ); if( error ) { TTF_SetFTError( "Couldn't set font size", error ); TTF_CloseFont( font ); return NULL; } /* Get the scalable font metrics for this font */ scale = face->size->metrics.y_scale; font->ascent = FT_CEIL(FT_MulFix(face->ascender, scale)); font->descent = FT_CEIL(FT_MulFix(face->descender, scale)); font->height = font->ascent - font->descent + /* baseline */ 1; font->lineskip = FT_CEIL(FT_MulFix(face->height, scale)); font->underline_offset = FT_FLOOR(FT_MulFix(face->underline_position, scale)); font->underline_height = FT_FLOOR(FT_MulFix(face->underline_thickness, scale)); } else { /* Non-scalable font case. ptsize determines which family * or series of fonts to grab from the non-scalable format. * It is not the point size of the font. * */ if ( ptsize >= font->face->num_fixed_sizes ) ptsize = font->face->num_fixed_sizes - 1; font->font_size_family = ptsize; error = FT_Set_Pixel_Sizes( face, face->available_sizes[ptsize].height, face->available_sizes[ptsize].width ); /* With non-scalale fonts, Freetype2 likes to fill many of the * font metrics with the value of 0. The size of the * non-scalable fonts must be determined differently * or sometimes cannot be determined. * */ font->ascent = face->available_sizes[ptsize].height; font->descent = 0; font->height = face->available_sizes[ptsize].height; font->lineskip = FT_CEIL(font->ascent); font->underline_offset = FT_FLOOR(face->underline_position); font->underline_height = FT_FLOOR(face->underline_thickness); } if ( font->underline_height < 1 ) { font->underline_height = 1; } #ifdef DEBUG_FONTS printf("Font metrics:\n"); printf("\tascent = %d, descent = %d\n", font->ascent, font->descent); printf("\theight = %d, lineskip = %d\n", font->height, font->lineskip); printf("\tunderline_offset = %d, underline_height = %d\n", font->underline_offset, font->underline_height); #endif /* Set the default font style */ font->style = TTF_STYLE_NORMAL; font->glyph_overhang = face->size->metrics.y_ppem / 10; /* x offset = cos(((90.0-12)/360)*2*M_PI), or 12 degree angle */ font->glyph_italics = 0.207f; font->glyph_italics *= font->height; return font; } TTF_Font* TTF_OpenFontRW( SDL_RWops *src, int freesrc, int ptsize ) { return TTF_OpenFontIndexRW(src, freesrc, ptsize, 0); } TTF_Font* TTF_OpenFontIndex( const char *file, int ptsize, long index ) { SDL_RWops *rw = SDL_RWFromFile(file, "rb"); if ( rw == NULL ) { TTF_SetError(SDL_GetError()); return NULL; } return TTF_OpenFontIndexRW(rw, 1, ptsize, index); } TTF_Font* TTF_OpenFont( const char *file, int ptsize ) { return TTF_OpenFontIndex(file, ptsize, 0); } static void Flush_Glyph( c_glyph* glyph ) { glyph->stored = 0; glyph->index = 0; if( glyph->bitmap.buffer ) { free( glyph->bitmap.buffer ); glyph->bitmap.buffer = 0; } if( glyph->pixmap.buffer ) { free( glyph->pixmap.buffer ); glyph->pixmap.buffer = 0; } glyph->cached = 0; } static void Flush_Cache( TTF_Font* font ) { int i; int size = sizeof( font->cache ) / sizeof( font->cache[0] ); for( i = 0; i < size; ++i ) { if( font->cache[i].cached ) { Flush_Glyph( &font->cache[i] ); } } if( font->scratch.cached ) { Flush_Glyph( &font->scratch ); } } static FT_Error Load_Glyph( TTF_Font* font, Uint16 ch, c_glyph* cached, int want ) { FT_Face face; FT_Error error; FT_GlyphSlot glyph; FT_Glyph_Metrics* metrics; FT_Outline* outline; if ( !font || !font->face ) { return FT_Err_Invalid_Handle; } face = font->face; /* Load the glyph */ if ( ! cached->index ) { cached->index = FT_Get_Char_Index( face, ch ); } error = FT_Load_Glyph( face, cached->index, FT_LOAD_DEFAULT ); if( error ) { return error; } /* Get our glyph shortcuts */ glyph = face->glyph; metrics = &glyph->metrics; outline = &glyph->outline; /* Get the glyph metrics if desired */ if ( (want & CACHED_METRICS) && !(cached->stored & CACHED_METRICS) ) { if ( FT_IS_SCALABLE( face ) ) { /* Get the bounding box */ cached->minx = FT_FLOOR(metrics->horiBearingX); cached->maxx = cached->minx + FT_CEIL(metrics->width); cached->maxy = FT_FLOOR(metrics->horiBearingY); cached->miny = cached->maxy - FT_CEIL(metrics->height); cached->yoffset = font->ascent - cached->maxy; cached->advance = FT_CEIL(metrics->horiAdvance); } else { /* Get the bounding box for non-scalable format. * Again, freetype2 fills in many of the font metrics * with the value of 0, so some of the values we * need must be calculated differently with certain * assumptions about non-scalable formats. * */ cached->minx = FT_FLOOR(metrics->horiBearingX); cached->maxx = cached->minx + FT_CEIL(metrics->horiAdvance); cached->maxy = FT_FLOOR(metrics->horiBearingY); cached->miny = cached->maxy - FT_CEIL(face->available_sizes[font->font_size_family].height); cached->yoffset = 0; cached->advance = FT_CEIL(metrics->horiAdvance); } /* Adjust for bold and italic text */ if( font->style & TTF_STYLE_BOLD ) { cached->maxx += font->glyph_overhang; } if( font->style & TTF_STYLE_ITALIC ) { cached->maxx += (int)ceil(font->glyph_italics); } cached->stored |= CACHED_METRICS; } if ( ((want & CACHED_BITMAP) && !(cached->stored & CACHED_BITMAP)) || ((want & CACHED_PIXMAP) && !(cached->stored & CACHED_PIXMAP)) ) { int mono = (want & CACHED_BITMAP); int i; FT_Bitmap* src; FT_Bitmap* dst; /* Handle the italic style */ if( font->style & TTF_STYLE_ITALIC ) { FT_Matrix shear; shear.xx = 1 << 16; shear.xy = (int) ( font->glyph_italics * ( 1 << 16 ) ) / font->height; shear.yx = 0; shear.yy = 1 << 16; FT_Outline_Transform( outline, &shear ); } /* Render the glyph */ if ( mono ) { error = FT_Render_Glyph( glyph, ft_render_mode_mono ); } else { error = FT_Render_Glyph( glyph, ft_render_mode_normal ); } if( error ) { return error; } /* Copy over information to cache */ src = &glyph->bitmap; if ( mono ) { dst = &cached->bitmap; } else { dst = &cached->pixmap; } memcpy( dst, src, sizeof( *dst ) ); /* FT_Render_Glyph() and .fon fonts always generate a * two-color (black and white) glyphslot surface, even * when rendered in ft_render_mode_normal. This is probably * a freetype2 bug because it is inconsistent with the * freetype2 documentation under FT_Render_Mode section. * */ if ( mono || !FT_IS_SCALABLE(face) ) { dst->pitch *= 8; } /* Adjust for bold and italic text */ if( font->style & TTF_STYLE_BOLD ) { int bump = font->glyph_overhang; dst->pitch += bump; dst->width += bump; } if( font->style & TTF_STYLE_ITALIC ) { int bump = (int)ceil(font->glyph_italics); dst->pitch += bump; dst->width += bump; } if (dst->rows != 0) { dst->buffer = (unsigned char *)malloc( dst->pitch * dst->rows ); if( !dst->buffer ) { return FT_Err_Out_Of_Memory; } memset( dst->buffer, 0, dst->pitch * dst->rows ); for( i = 0; i < src->rows; i++ ) { int soffset = i * src->pitch; int doffset = i * dst->pitch; if ( mono ) { unsigned char *srcp = src->buffer + soffset; unsigned char *dstp = dst->buffer + doffset; int j; for ( j = 0; j < src->width; j += 8 ) { unsigned char ch = *srcp++; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; } } else if ( !FT_IS_SCALABLE(face) ) { /* This special case wouldn't * be here if the FT_Render_Glyph() * function wasn't buggy when it tried * to render a .fon font with 256 * shades of gray. Instead, it * returns a black and white surface * and we have to translate it back * to a 256 gray shaded surface. * */ unsigned char *srcp = src->buffer + soffset; unsigned char *dstp = dst->buffer + doffset; unsigned char ch; int j, k; for ( j = 0; j < src->width; j += 8) { ch = *srcp++; for (k = 0; k < 8; ++k) { if ((ch&0x80) >> 7) { *dstp++ = NUM_GRAYS - 1; } else { *dstp++ = 0x00; } ch <<= 1; } } } else { memcpy(dst->buffer+doffset, src->buffer+soffset, src->pitch); } } } /* Handle the bold style */ if ( font->style & TTF_STYLE_BOLD ) { int row; int col; int offset; int pixel; Uint8* pixmap; /* The pixmap is a little hard, we have to add and clamp */ for( row = dst->rows - 1; row >= 0; --row ) { pixmap = (Uint8*) dst->buffer + row * dst->pitch; for( offset=1; offset <= font->glyph_overhang; ++offset ) { for( col = dst->width - 1; col > 0; --col ) { pixel = (pixmap[col] + pixmap[col-1]); if( pixel > NUM_GRAYS - 1 ) { pixel = NUM_GRAYS - 1; } pixmap[col] = (Uint8) pixel; } } } } /* Mark that we rendered this format */ if ( mono ) { cached->stored |= CACHED_BITMAP; } else { cached->stored |= CACHED_PIXMAP; } } /* We're done, mark this glyph cached */ cached->cached = ch; return 0; } static FT_Error Find_Glyph( TTF_Font* font, Uint16 ch, int want ) { int retval = 0; if( ch < 256 ) { font->current = &font->cache[ch]; } else { if ( font->scratch.cached != ch ) { Flush_Glyph( &font->scratch ); } font->current = &font->scratch; } if ( (font->current->stored & want) != want ) { retval = Load_Glyph( font, ch, font->current, want ); } return retval; } void TTF_CloseFont( TTF_Font* font ) { Flush_Cache( font ); if ( font->face ) { FT_Done_Face( font->face ); } if ( font->args.stream ) { free( font->args.stream ); } if ( font->freesrc ) { SDL_RWclose( font->src ); } free( font ); } static Uint16 *LATIN1_to_UNICODE(Uint16 *unicode, const char *text, int len) { int i; for ( i=0; i < len; ++i ) { unicode[i] = ((const unsigned char *)text)[i]; } unicode[i] = 0; return unicode; } static Uint16 *UTF8_to_UNICODE(Uint16 *unicode, const char *utf8, int len) { int i, j; Uint16 ch; for ( i=0, j=0; i < len; ++i, ++j ) { ch = ((const unsigned char *)utf8)[i]; if ( ch >= 0xF0 ) { ch = (Uint16)(utf8[i]&0x07) << 18; ch |= (Uint16)(utf8[++i]&0x3F) << 12; ch |= (Uint16)(utf8[++i]&0x3F) << 6; ch |= (Uint16)(utf8[++i]&0x3F); } else if ( ch >= 0xE0 ) { ch = (Uint16)(utf8[i]&0x0F) << 12; ch |= (Uint16)(utf8[++i]&0x3F) << 6; ch |= (Uint16)(utf8[++i]&0x3F); } else if ( ch >= 0xC0 ) { ch = (Uint16)(utf8[i]&0x1F) << 6; ch |= (Uint16)(utf8[++i]&0x3F); } unicode[j] = ch; } unicode[j] = 0; return unicode; } int TTF_FontHeight(TTF_Font *font) { return(font->height); } int TTF_FontAscent(TTF_Font *font) { return(font->ascent); } int TTF_FontDescent(TTF_Font *font) { return(font->descent); } int TTF_FontLineSkip(TTF_Font *font) { return(font->lineskip); } long TTF_FontFaces(TTF_Font *font) { return(font->face->num_faces); } int TTF_FontFaceIsFixedWidth(TTF_Font *font) { return(FT_IS_FIXED_WIDTH(font->face)); } char *TTF_FontFaceFamilyName(TTF_Font *font) { return(font->face->family_name); } char *TTF_FontFaceStyleName(TTF_Font *font) { return(font->face->style_name); } int TTF_GlyphMetrics(TTF_Font *font, Uint16 ch, int* minx, int* maxx, int* miny, int* maxy, int* advance) { FT_Error error; error = Find_Glyph(font, ch, CACHED_METRICS); if ( error ) { TTF_SetFTError("Couldn't find glyph", error); return -1; } if ( minx ) { *minx = font->current->minx; } if ( maxx ) { *maxx = font->current->maxx; if( font->style & TTF_STYLE_BOLD ) { *maxx += font->glyph_overhang; } } if ( miny ) { *miny = font->current->miny; } if ( maxy ) { *maxy = font->current->maxy; } if ( advance ) { *advance = font->current->advance; if( font->style & TTF_STYLE_BOLD ) { *advance += font->glyph_overhang; } } return 0; } int TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h) { Uint16 *unicode_text; int unicode_len; int status; /* Copy the Latin-1 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return -1; } *unicode_text = UNICODE_BOM_NATIVE; LATIN1_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ status = TTF_SizeUNICODE(font, unicode_text, w, h); /* Free the text buffer and return */ FREEA(unicode_text); return status; } int TTF_SizeUTF8(TTF_Font *font, const char *text, int *w, int *h) { Uint16 *unicode_text; int unicode_len; int status; /* Copy the UTF-8 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return -1; } *unicode_text = UNICODE_BOM_NATIVE; UTF8_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ status = TTF_SizeUNICODE(font, unicode_text, w, h); /* Free the text buffer and return */ FREEA(unicode_text); return status; } int TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h) { int status; const Uint16 *ch; int swapped; int x, z; int minx, maxx; int miny, maxy; c_glyph *glyph; FT_Error error; FT_Long use_kerning; FT_UInt prev_index = 0; /* Initialize everything to 0 */ if ( ! TTF_initialized ) { TTF_SetError( "Library not initialized" ); return -1; } status = 0; minx = maxx = 0; miny = maxy = 0; swapped = TTF_byteswapped; /* check kerning */ use_kerning = FT_HAS_KERNING( font->face ); /* Load each character and sum it's bounding box */ x= 0; for ( ch=text; *ch; ++ch ) { Uint16 c = *ch; if ( c == UNICODE_BOM_NATIVE ) { swapped = 0; if ( text == ch ) { ++text; } continue; } if ( c == UNICODE_BOM_SWAPPED ) { swapped = 1; if ( text == ch ) { ++text; } continue; } if ( swapped ) { c = SDL_Swap16(c); } error = Find_Glyph(font, c, CACHED_METRICS); if ( error ) { return -1; } glyph = font->current; /* handle kerning */ if ( use_kerning && prev_index && glyph->index ) { FT_Vector delta; FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); x += delta.x >> 6; } #if 0 if ( (ch == text) && (glyph->minx < 0) ) { /* Fixes the texture wrapping bug when the first letter * has a negative minx value or horibearing value. The entire * bounding box must be adjusted to be bigger so the entire * letter can fit without any texture corruption or wrapping. * * Effects: First enlarges bounding box. * Second, xstart has to start ahead of its normal spot in the * negative direction of the negative minx value. * (pushes everything to the right). * * This will make the memory copy of the glyph bitmap data * work out correctly. * */ z -= glyph->minx; } #endif z = x + glyph->minx; if ( minx > z ) { minx = z; } if ( font->style & TTF_STYLE_BOLD ) { x += font->glyph_overhang; } if ( glyph->advance > glyph->maxx ) { z = x + glyph->advance; } else { z = x + glyph->maxx; } if ( maxx < z ) { maxx = z; } x += glyph->advance; if ( glyph->miny < miny ) { miny = glyph->miny; } if ( glyph->maxy > maxy ) { maxy = glyph->maxy; } prev_index = glyph->index; } /* Fill the bounds rectangle */ if ( w ) { *w = (maxx - minx); } if ( h ) { #if 0 /* This is correct, but breaks many applications */ *h = (maxy - miny); #else *h = font->height; #endif } return status; } /* Convert the Latin-1 text to UNICODE and render it */ SDL_Surface *TTF_RenderText_Solid(TTF_Font *font, const char *text, SDL_Color fg) { SDL_Surface *textbuf; Uint16 *unicode_text; int unicode_len; /* Copy the Latin-1 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return(NULL); } *unicode_text = UNICODE_BOM_NATIVE; LATIN1_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ textbuf = TTF_RenderUNICODE_Solid(font, unicode_text, fg); /* Free the text buffer and return */ FREEA(unicode_text); return(textbuf); } /* Convert the UTF-8 text to UNICODE and render it */ SDL_Surface *TTF_RenderUTF8_Solid(TTF_Font *font, const char *text, SDL_Color fg) { SDL_Surface *textbuf; Uint16 *unicode_text; int unicode_len; /* Copy the UTF-8 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return(NULL); } *unicode_text = UNICODE_BOM_NATIVE; UTF8_to_UNICODE(unicode_text, text, unicode_len); /* Render the new text */ textbuf = TTF_RenderUNICODE_Solid(font, unicode_text, fg); /* Free the text buffer and return */ FREEA(unicode_text); return(textbuf); } SDL_Surface *TTF_RenderUNICODE_Solid(TTF_Font *font, const Uint16 *text, SDL_Color fg) { int xstart; int width; int height; SDL_Surface* textbuf; SDL_Palette* palette; const Uint16* ch; Uint8* src; Uint8* dst; Uint8 *dst_check; int swapped; int row, col; c_glyph *glyph; FT_Bitmap *current; FT_Error error; FT_Long use_kerning; FT_UInt prev_index = 0; /* Get the dimensions of the text surface */ if( ( TTF_SizeUNICODE(font, text, &width, NULL) < 0 ) || !width ) { TTF_SetError( "Text has zero width" ); return NULL; } height = font->height; /* Create the target surface */ textbuf = SDL_AllocSurface(SDL_SWSURFACE, width, height, 8, 0, 0, 0, 0); if( textbuf == NULL ) { return NULL; } /* Adding bound checking to avoid all kinds of memory corruption errors that may occur. */ dst_check = (Uint8*)textbuf->pixels + textbuf->pitch * textbuf->h; /* Fill the palette with the foreground color */ palette = textbuf->format->palette; palette->colors[0].r = 255 - fg.r; palette->colors[0].g = 255 - fg.g; palette->colors[0].b = 255 - fg.b; palette->colors[1].r = fg.r; palette->colors[1].g = fg.g; palette->colors[1].b = fg.b; SDL_SetColorKey( textbuf, SDL_SRCCOLORKEY, 0 ); /* check kerning */ use_kerning = FT_HAS_KERNING( font->face ); /* Load and render each character */ xstart = 0; swapped = TTF_byteswapped; for( ch=text; *ch; ++ch ) { Uint16 c = *ch; if ( c == UNICODE_BOM_NATIVE ) { swapped = 0; if ( text == ch ) { ++text; } continue; } if ( c == UNICODE_BOM_SWAPPED ) { swapped = 1; if ( text == ch ) { ++text; } continue; } if ( swapped ) { c = SDL_Swap16(c); } error = Find_Glyph(font, c, CACHED_METRICS|CACHED_BITMAP); if( error ) { SDL_FreeSurface( textbuf ); return NULL; } glyph = font->current; current = &glyph->bitmap; /* Ensure the width of the pixmap is correct. On some cases, * freetype may report a larger pixmap than possible.*/ width = current->width; if (width > glyph->maxx - glyph->minx) { width = glyph->maxx - glyph->minx; } /* do kerning, if possible AC-Patch */ if ( use_kerning && prev_index && glyph->index ) { FT_Vector delta; FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); xstart += delta.x >> 6; } /* Compensate for wrap around bug with negative minx's */ if ( (ch == text) && (glyph->minx < 0) ) { xstart -= glyph->minx; } for( row = 0; row < current->rows; ++row ) { /* Make sure we don't go either over, or under the * limit */ if ( row+glyph->yoffset < 0 ) { continue; } if ( row+glyph->yoffset >= textbuf->h ) { continue; } dst = (Uint8*) textbuf->pixels + (row+glyph->yoffset) * textbuf->pitch + xstart + glyph->minx; src = current->buffer + row * current->pitch; for ( col=width; col>0 && dst < dst_check; --col ) { *dst++ |= *src++; } } xstart += glyph->advance; if ( font->style & TTF_STYLE_BOLD ) { xstart += font->glyph_overhang; } prev_index = glyph->index; } /* Handle the underline style */ if( font->style & TTF_STYLE_UNDERLINE ) { row = font->ascent - font->underline_offset - 1; if ( row >= textbuf->h) { row = (textbuf->h-1) - font->underline_height; } dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch; for ( row=font->underline_height; row>0; --row ) { /* 1 because 0 is the bg color */ memset( dst, 1, textbuf->w ); dst += textbuf->pitch; } } return textbuf; } SDL_Surface *TTF_RenderGlyph_Solid(TTF_Font *font, Uint16 ch, SDL_Color fg) { SDL_Surface *textbuf; SDL_Palette *palette; Uint8 *src, *dst; int row; FT_Error error; c_glyph *glyph; /* Get the glyph itself */ error = Find_Glyph(font, ch, CACHED_METRICS|CACHED_BITMAP); if ( error ) { return(NULL); } glyph = font->current; /* Create the target surface */ textbuf = SDL_CreateRGBSurface( SDL_SWSURFACE, glyph->bitmap.pitch, glyph->bitmap.rows, 8, 0, 0, 0, 0 ); if ( ! textbuf ) { return(NULL); } /* Fill the palette with the foreground color */ palette = textbuf->format->palette; palette->colors[0].r = 255-fg.r; palette->colors[0].g = 255-fg.g; palette->colors[0].b = 255-fg.b; palette->colors[1].r = fg.r; palette->colors[1].g = fg.g; palette->colors[1].b = fg.b; SDL_SetColorKey(textbuf, SDL_SRCCOLORKEY, 0); /* Copy the character from the pixmap */ src = glyph->bitmap.buffer; dst = (Uint8*) textbuf->pixels; for ( row = 0; row < textbuf->h; ++row ) { memcpy( dst, src, glyph->bitmap.pitch ); src += glyph->bitmap.pitch; dst += textbuf->pitch; } /* Handle the underline style */ if( font->style & TTF_STYLE_UNDERLINE ) { row = font->ascent - font->underline_offset - 1; if ( row >= textbuf->h) { row = (textbuf->h-1) - font->underline_height; } dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch; for ( row=font->underline_height; row>0; --row ) { /* 1 because 0 is the bg color */ memset( dst, 1, textbuf->w ); dst += textbuf->pitch; } } return(textbuf); } /* Convert the Latin-1 text to UNICODE and render it */ SDL_Surface *TTF_RenderText_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg) { SDL_Surface *textbuf; Uint16 *unicode_text; int unicode_len; /* Copy the Latin-1 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return(NULL); } *unicode_text = UNICODE_BOM_NATIVE; LATIN1_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ textbuf = TTF_RenderUNICODE_Shaded(font, unicode_text, fg, bg); /* Free the text buffer and return */ FREEA(unicode_text); return(textbuf); } /* Convert the UTF-8 text to UNICODE and render it */ SDL_Surface *TTF_RenderUTF8_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg) { SDL_Surface *textbuf; Uint16 *unicode_text; int unicode_len; /* Copy the UTF-8 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return(NULL); } *unicode_text = UNICODE_BOM_NATIVE; UTF8_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ textbuf = TTF_RenderUNICODE_Shaded(font, unicode_text, fg, bg); /* Free the text buffer and return */ FREEA(unicode_text); return(textbuf); } SDL_Surface* TTF_RenderUNICODE_Shaded( TTF_Font* font, const Uint16* text, SDL_Color fg, SDL_Color bg ) { int xstart; int width; int height; SDL_Surface* textbuf; SDL_Palette* palette; int index; int rdiff; int gdiff; int bdiff; const Uint16* ch; Uint8* src; Uint8* dst; Uint8* dst_check; int swapped; int row, col; FT_Bitmap* current; c_glyph *glyph; FT_Error error; FT_Long use_kerning; FT_UInt prev_index = 0; /* Get the dimensions of the text surface */ if( ( TTF_SizeUNICODE(font, text, &width, NULL) < 0 ) || !width ) { TTF_SetError("Text has zero width"); return NULL; } height = font->height; /* Create the target surface */ textbuf = SDL_AllocSurface(SDL_SWSURFACE, width, height, 8, 0, 0, 0, 0); if( textbuf == NULL ) { return NULL; } /* Adding bound checking to avoid all kinds of memory corruption errors that may occur. */ dst_check = (Uint8*)textbuf->pixels + textbuf->pitch * textbuf->h; /* Fill the palette with NUM_GRAYS levels of shading from bg to fg */ palette = textbuf->format->palette; rdiff = fg.r - bg.r; gdiff = fg.g - bg.g; bdiff = fg.b - bg.b; for( index = 0; index < NUM_GRAYS; ++index ) { palette->colors[index].r = bg.r + (index*rdiff) / (NUM_GRAYS-1); palette->colors[index].g = bg.g + (index*gdiff) / (NUM_GRAYS-1); palette->colors[index].b = bg.b + (index*bdiff) / (NUM_GRAYS-1); } /* check kerning */ use_kerning = FT_HAS_KERNING( font->face ); /* Load and render each character */ xstart = 0; swapped = TTF_byteswapped; for( ch = text; *ch; ++ch ) { Uint16 c = *ch; if ( c == UNICODE_BOM_NATIVE ) { swapped = 0; if ( text == ch ) { ++text; } continue; } if ( c == UNICODE_BOM_SWAPPED ) { swapped = 1; if ( text == ch ) { ++text; } continue; } if ( swapped ) { c = SDL_Swap16(c); } error = Find_Glyph(font, c, CACHED_METRICS|CACHED_PIXMAP); if( error ) { SDL_FreeSurface( textbuf ); return NULL; } glyph = font->current; /* Ensure the width of the pixmap is correct. On some cases, * freetype may report a larger pixmap than possible.*/ width = glyph->pixmap.width; if (width > glyph->maxx - glyph->minx) { width = glyph->maxx - glyph->minx; } /* do kerning, if possible AC-Patch */ if ( use_kerning && prev_index && glyph->index ) { FT_Vector delta; FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); xstart += delta.x >> 6; } /* Compensate for the wrap around with negative minx's */ if ( (ch == text) && (glyph->minx < 0) ) { xstart -= glyph->minx; } current = &glyph->pixmap; for( row = 0; row < current->rows; ++row ) { /* Make sure we don't go either over, or under the * limit */ if ( row+glyph->yoffset < 0 ) { continue; } if ( row+glyph->yoffset >= textbuf->h ) { continue; } dst = (Uint8*) textbuf->pixels + (row+glyph->yoffset) * textbuf->pitch + xstart + glyph->minx; src = current->buffer + row * current->pitch; for ( col=width; col>0 && dst < dst_check; --col ) { *dst++ |= *src++; } } xstart += glyph->advance; if( font->style & TTF_STYLE_BOLD ) { xstart += font->glyph_overhang; } prev_index = glyph->index; } /* Handle the underline style */ if( font->style & TTF_STYLE_UNDERLINE ) { row = font->ascent - font->underline_offset - 1; if ( row >= textbuf->h) { row = (textbuf->h-1) - font->underline_height; } dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch; for ( row=font->underline_height; row>0; --row ) { memset( dst, NUM_GRAYS - 1, textbuf->w ); dst += textbuf->pitch; } } return textbuf; } SDL_Surface* TTF_RenderGlyph_Shaded( TTF_Font* font, Uint16 ch, SDL_Color fg, SDL_Color bg ) { SDL_Surface* textbuf; SDL_Palette* palette; int index; int rdiff; int gdiff; int bdiff; Uint8* src; Uint8* dst; int row; FT_Error error; c_glyph* glyph; /* Get the glyph itself */ error = Find_Glyph(font, ch, CACHED_METRICS|CACHED_PIXMAP); if( error ) { return NULL; } glyph = font->current; /* Create the target surface */ textbuf = SDL_CreateRGBSurface( SDL_SWSURFACE, glyph->pixmap.width, glyph->pixmap.rows, 8, 0, 0, 0, 0 ); if( !textbuf ) { return NULL; } /* Fill the palette with NUM_GRAYS levels of shading from bg to fg */ palette = textbuf->format->palette; rdiff = fg.r - bg.r; gdiff = fg.g - bg.g; bdiff = fg.b - bg.b; for( index = 0; index < NUM_GRAYS; ++index ) { palette->colors[index].r = bg.r + (index*rdiff) / (NUM_GRAYS-1); palette->colors[index].g = bg.g + (index*gdiff) / (NUM_GRAYS-1); palette->colors[index].b = bg.b + (index*bdiff) / (NUM_GRAYS-1); } /* Copy the character from the pixmap */ src = glyph->pixmap.buffer; dst = (Uint8*) textbuf->pixels; for ( row = 0; row < textbuf->h; ++row ) { memcpy( dst, src, glyph->pixmap.pitch ); src += glyph->pixmap.pitch; dst += textbuf->pitch; } /* Handle the underline style */ if( font->style & TTF_STYLE_UNDERLINE ) { row = font->ascent - font->underline_offset - 1; if ( row >= textbuf->h) { row = (textbuf->h-1) - font->underline_height; } dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch; for ( row=font->underline_height; row>0; --row ) { memset( dst, NUM_GRAYS - 1, textbuf->w ); dst += textbuf->pitch; } } return textbuf; } /* Convert the Latin-1 text to UNICODE and render it */ SDL_Surface *TTF_RenderText_Blended(TTF_Font *font, const char *text, SDL_Color fg) { SDL_Surface *textbuf; Uint16 *unicode_text; int unicode_len; /* Copy the Latin-1 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return(NULL); } *unicode_text = UNICODE_BOM_NATIVE; LATIN1_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ textbuf = TTF_RenderUNICODE_Blended(font, unicode_text, fg); /* Free the text buffer and return */ FREEA(unicode_text); return(textbuf); } /* Convert the UTF-8 text to UNICODE and render it */ SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *font, const char *text, SDL_Color fg) { SDL_Surface *textbuf; Uint16 *unicode_text; int unicode_len; /* Copy the UTF-8 text to a UNICODE text buffer */ unicode_len = strlen(text); unicode_text = (Uint16 *)ALLOCA((1+unicode_len+1)*(sizeof *unicode_text)); if ( unicode_text == NULL ) { TTF_SetError("Out of memory"); return(NULL); } *unicode_text = UNICODE_BOM_NATIVE; UTF8_to_UNICODE(unicode_text+1, text, unicode_len); /* Render the new text */ textbuf = TTF_RenderUNICODE_Blended(font, unicode_text, fg); /* Free the text buffer and return */ FREEA(unicode_text); return(textbuf); } SDL_Surface *TTF_RenderUNICODE_Blended(TTF_Font *font, const Uint16 *text, SDL_Color fg) { int xstart; int width, height; SDL_Surface *textbuf; Uint32 alpha; Uint32 pixel; const Uint16 *ch; Uint8 *src; Uint32 *dst; Uint32 *dst_check; int swapped; int row, col; c_glyph *glyph; FT_Error error; FT_Long use_kerning; FT_UInt prev_index = 0; const SDL_VideoInfo *video_info; /* Get the dimensions of the text surface */ if ( (TTF_SizeUNICODE(font, text, &width, NULL) < 0) || !width ) { TTF_SetError("Text has zero width"); return(NULL); } height = font->height; /* Use optimium pixel masks for the video hardware */ video_info = SDL_GetVideoInfo(); if (video_info->vfmt->Rmask < video_info->vfmt->Bmask) { textbuf = SDL_AllocSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); } else { textbuf = SDL_AllocSurface(SDL_SWSURFACE, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); } if ( textbuf == NULL ) { return(NULL); } /* Adding bound checking to avoid all kinds of memory corruption errors that may occur. */ dst_check = (Uint32*)textbuf->pixels + textbuf->pitch/4 * textbuf->h; /* check kerning */ use_kerning = FT_HAS_KERNING( font->face ); /* Load and render each character */ xstart = 0; swapped = TTF_byteswapped; pixel = SDL_MapRGBA(textbuf->format, fg.r, fg.g, fg.b, 0x00); SDL_FillRect(textbuf, NULL, pixel); /* Initialize with fg and 0 alpha */ for ( ch=text; *ch; ++ch ) { Uint16 c = *ch; if ( c == UNICODE_BOM_NATIVE ) { swapped = 0; if ( text == ch ) { ++text; } continue; } if ( c == UNICODE_BOM_SWAPPED ) { swapped = 1; if ( text == ch ) { ++text; } continue; } if ( swapped ) { c = SDL_Swap16(c); } error = Find_Glyph(font, c, CACHED_METRICS|CACHED_PIXMAP); if( error ) { SDL_FreeSurface( textbuf ); return NULL; } glyph = font->current; /* Ensure the width of the pixmap is correct. On some cases, * freetype may report a larger pixmap than possible.*/ width = glyph->pixmap.width; if (width > glyph->maxx - glyph->minx) { width = glyph->maxx - glyph->minx; } /* do kerning, if possible AC-Patch */ if ( use_kerning && prev_index && glyph->index ) { FT_Vector delta; FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); xstart += delta.x >> 6; } /* Compensate for the wrap around bug with negative minx's */ if ( (ch == text) && (glyph->minx < 0) ) { xstart -= glyph->minx; } for ( row = 0; row < glyph->pixmap.rows; ++row ) { /* Make sure we don't go either over, or under the * limit */ if ( row+glyph->yoffset < 0 ) { continue; } if ( row+glyph->yoffset >= textbuf->h ) { continue; } dst = (Uint32*) textbuf->pixels + (row+glyph->yoffset) * textbuf->pitch/4 + xstart + glyph->minx; /* Added code to adjust src pointer for pixmaps to * account for pitch. * */ src = (Uint8*) (glyph->pixmap.buffer + glyph->pixmap.pitch * row); for ( col = width; col>0 && dst < dst_check; --col) { alpha = *src++; *dst++ |= pixel | (alpha << 24); } } xstart += glyph->advance; if ( font->style & TTF_STYLE_BOLD ) { xstart += font->glyph_overhang; } prev_index = glyph->index; } /* Handle the underline style */ if( font->style & TTF_STYLE_UNDERLINE ) { row = font->ascent - font->underline_offset - 1; if ( row >= textbuf->h) { row = (textbuf->h-1) - font->underline_height; } dst = (Uint32 *)textbuf->pixels + row * textbuf->pitch/4; pixel |= 0xFF000000; /* Amask */ for ( row=font->underline_height; row>0; --row ) { for ( col=0; col < textbuf->w; ++col ) { dst[col] = pixel; } dst += textbuf->pitch/4; } } return(textbuf); } SDL_Surface *TTF_RenderGlyph_Blended(TTF_Font *font, Uint16 ch, SDL_Color fg) { SDL_Surface *textbuf; Uint32 alpha; Uint32 pixel; Uint8 *src; Uint32 *dst; int row, col; FT_Error error; c_glyph *glyph; /* Get the glyph itself */ error = Find_Glyph(font, ch, CACHED_METRICS|CACHED_PIXMAP); if ( error ) { return(NULL); } glyph = font->current; textbuf = SDL_CreateRGBSurface(SDL_SWSURFACE, glyph->pixmap.width, glyph->pixmap.rows, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if ( ! textbuf ) { return(NULL); } /* Copy the character from the pixmap */ pixel = (fg.r<<16)|(fg.g<<8)|fg.b; SDL_FillRect(textbuf, NULL, pixel); /* Initialize with fg and 0 alpha */ for ( row=0; rowh; ++row ) { /* Changed src to take pitch into account, not just width */ src = glyph->pixmap.buffer + row * glyph->pixmap.pitch; dst = (Uint32 *)textbuf->pixels + row * textbuf->pitch/4; for ( col=0; colpixmap.width; ++col ) { alpha = *src++; *dst++ = pixel | (alpha << 24); } } /* Handle the underline style */ if( font->style & TTF_STYLE_UNDERLINE ) { row = font->ascent - font->underline_offset - 1; if ( row >= textbuf->h) { row = (textbuf->h-1) - font->underline_height; } dst = (Uint32 *)textbuf->pixels + row * textbuf->pitch/4; pixel |= 0xFF000000; /* Amask */ for ( row=font->underline_height; row>0; --row ) { for ( col=0; col < textbuf->w; ++col ) { dst[col] = pixel; } dst += textbuf->pitch/4; } } return(textbuf); } void TTF_SetFontStyle( TTF_Font* font, int style ) { font->style = style; Flush_Cache( font ); } int TTF_GetFontStyle( TTF_Font* font ) { return font->style; } void TTF_Quit( void ) { if ( TTF_initialized ) { if ( --TTF_initialized == 0 ) { FT_Done_FreeType( library ); } } } int TTF_WasInit( void ) { return TTF_initialized; }