Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Can you use any function to render more than a line of text in a surface?

If you can't do this, what whould be the way to go? I am doing the following: Create a surface (not sure if should be a software or a hardware one) and then create as many surfaces as lines my text have, then blitting all the surfaces, and freeing all the surfaces. But this turns to be very slow. This is the code:

void Text::updateSurface() {
    if (surface != NULL) {
        SDL_FreeSurface(surface);
    }

    TTF_Font * font = TTF_OpenFont(fontPath.c_str(),size);
    if (font == NULL) {
        ERROR("Null font"<<TTF_GetError());
    }

    int maxWidth = 0;
    int totalHeight = 0;

    std::vector<SDL_Surface *> surfaces;
    for (size_t i=0; i<lines.size(); i++) {
        SDL_Surface * partialSurface = TTF_RenderText_Solid(font, lines[i].c_str(), color);
        if (partialSurface == NULL) {
            ERROR("surface == NULL: "<<TTF_GetError());
        }
        surfaces.push_back(partialSurface);
        totalHeight += partialSurface->h;
        maxWidth = std::max(maxWidth, partialSurface->w);
    }

    TTF_CloseFont(font);

    surface = SDL::allocateSurface(maxWidth,totalHeight);
    if (backgroundColor != NULL) {
        SDL_FillRect(surface, NULL, SDL_MapRGB(surface->format,backgroundColor->r, backgroundColor->g, backgroundColor->b));
    }
    totalHeight = 0;
    for (size_t i = 0; i < surfaces.size(); i++) {
        SDL_Rect dst = {0, totalHeight, 0, 0};
        switch(aligment) {
        case ALIGMENT_RIGHT:
            dst.x = maxWidth - surfaces[i]->w;
            break;
        case ALIGMENT_CENTER:
            dst.x = (maxWidth - surfaces[i]->w) / 2;
            break;
        case ALIGMENT_LEFT:
        default:
            break;
        }
        totalHeight += surfaces[i]->h;
        SDL_BlitSurface(surfaces[i],NULL,surface,&dst);
        SDL_FreeSurface(surfaces[i]);
    }
}

This is the code for SDL::allocateSurface(...)

static SDL_Surface * allocateSurface(int width, int height, int bpp = -1, Uint32 flags = 0)

SDL_Surface * SDL::allocateSurface(int width, int height, int bpp, Uint32 flags) {
    if (bpp == -1) {
        bpp = SDL::bpp; //SDL_GetVideoInfo()->vfmt->BitsPerPixel;
    }

    if (flags == 0) {
        flags = SDL_HWSURFACE;
    }

    Uint32 rmask, gmask, bmask, amask;

    #if SDL_BYTEORDER == SDL_BIG_ENDIAN
        rmask = 0xff000000;
        gmask = 0x00ff0000;
        bmask = 0x0000ff00;
        amask = 0x000000ff;
    #else
        rmask = 0x000000ff;
        gmask = 0x0000ff00;
        bmask = 0x00ff0000;
        amask = 0xff000000;
    #endif

    return SDL_CreateRGBSurface(flags, width, height, bpp, rmask, gmask, bmask, amask);
}
share|improve this question
    
Why multiple surfaces? You can render the text all to one single surface. –  Josiah Hester Dec 26 '12 at 1:47
    
How? That question was the first line in my post :S –  Trollkemada Dec 26 '12 at 2:09
    
You'll need to get the Size of each line of text via TTF_SizeText(text,*w, *h) then using the information from their, you can create your own line breaks :). –  dan369 Dec 26 '12 at 2:22
    
Still I need one surface for each line, for the rendering. Am i wrong? –  Trollkemada Dec 26 '12 at 2:31
    
Well i'd think you'd need to have 2 surfaces. 1 for your current text line and then another for your result. You'd re-use your textLine surface to loop through each line and then render each line to your result image. But, to make things easier, there is this library you could use;code.bluedinosaurs.com/lib/NFont –  dan369 Dec 26 '12 at 4:59

4 Answers 4

up vote 1 down vote accepted

Don't render text directly with SDL_TTF. It's not efficient, at all. Use SDL_TTF to generate a glyph atlas, or use a tool like AngelCode BMFont. You can get glyph metrics (Width, Height, x advance, etc.) using TTF_LineSkip to find the vertical distance, and TTF_GlyphMetrics.

At this point, you can render text yourself. By putting the font glyphs into an atlas, and storing glyph metrics, you can easily render multiple lines, render each glyph in a different style or even font, do various effects, etc. By batching your glyph draws, you can be way more efficient than using SDL_TTF naively. There's no need to copy whole lines into temporary SDL buffers which are copied to your real output.

It's harder, but worth it. Easy toy render APIs are just that: toys.

share|improve this answer
    
Thanks, i'll look into that –  Trollkemada Jan 27 '13 at 11:47
    
I've seen this method used before but won't that ruin the Kerning of the font? If you're rendering a large block of text (hence the need for multi-lines) readability would be important. –  Cramer Oct 5 '13 at 5:08
    
You can query the kerning information between two glyphs and store that in an additional table. Most fonts in games are designed to be big, bold, and very easy to read at a single glance, and so only need very basic size metrics and no kerning, but it's definitely possible to have it in a pre-cached glyph map. –  Sean Middleditch Oct 5 '13 at 5:20
    
At this point, rendering fonts is not a huge computational task, especially if you are only doing a 2D game and you are basically describing rewriting the font system but use bitmaps rather than vectors. And if you don't need complicated metrics its easier just to pre-render the fonts at compile time –  CobaltHex Feb 25 at 17:59

Here's the exact code I use for generating a multi-line speech bubble. It's not the best, but it's worked for me so far. I use increments of 5 throughout the function since the sprites for the corners and edges of my speech bubbles are 5x5 pixels.

in-game example:

in-game implementation of function below

void speechbubble(SDL_Rect origin, int numlines, std::string quote1, std::string quote2, std::string quote3, SDL_Surface* screen)
{
const char* m_quote1 = quote1.c_str();
const char* m_quote2 = quote2.c_str();
const char* m_quote3 = quote3.c_str();
SDL_Surface *spritesheet = load_image("images/spritesheet.bmp");

SDL_Rect smallclip[8];
SDL_Rect m_origin = {origin.x + 5, origin.y + 5, origin.w - 5, origin.h - 5};

smallclip[0].x=0;smallclip[0].y=224;    //top left corner
smallclip[1].x=5;smallclip[1].y=224;    //top right corner
smallclip[2].x=10;smallclip[2].y=224;   //bot left corner
smallclip[3].x=15;smallclip[3].y=224;   //bot right corner
smallclip[4].x=0;smallclip[4].y=229;    //top surface
smallclip[5].x=5;smallclip[5].y=229;    //bot surface
smallclip[6].x=10;smallclip[6].y=229;   //left surface
smallclip[7].x=15;smallclip[7].y=229;   //right surface

for(int i =0;i<8;i++)
{
    smallclip[i].w = 5;
    smallclip[i].h = 5;
}

Uint32 white = SDL_MapRGB(screen->format,255,255,255);

SDL_FillRect(screen,&m_origin,white);

for(int i = 0; i<((origin.w-1)/5);i++)  //top and bottom surfaces
{
    apply_surface(origin.x+5 + (i*5), origin.y, spritesheet, screen, &smallclip[4]);
    apply_surface(origin.x+5 + (i*5), origin.y+origin.h, spritesheet, screen, &smallclip[5]);
}
apply_surface(origin.x+origin.w-5, origin.y, spritesheet, screen, &smallclip[4]);
apply_surface(origin.x+origin.w-5, origin.y+origin.h, spritesheet, screen, &smallclip[5]);

for(int i = 0; i<((origin.h-1)/5);i++)  //left and right surfaces
{
    apply_surface(origin.x, origin.y+5 + (i*5), spritesheet, screen, &smallclip[6]);
    apply_surface(origin.x+origin.w, origin.y+5 + (i*5), spritesheet, screen, &smallclip[7]);
}
apply_surface(origin.x, origin.y+origin.h-5, spritesheet, screen, &smallclip[6]);
apply_surface(origin.x+origin.w, origin.y+origin.h-5, spritesheet, screen, &smallclip[7]);

apply_surface(origin.x, origin.y,spritesheet,screen,&smallclip[0]);                     //apply the corners
apply_surface(origin.x+origin.w, origin.y,spritesheet,screen,&smallclip[1]);
apply_surface(origin.x, origin.y+origin.h,spritesheet,screen,&smallclip[2]);
apply_surface(origin.x+origin.w, origin.y+origin.h,spritesheet,screen,&smallclip[3]);

SDL_Surface* text_quote1 = NULL;
SDL_Surface* text_quote2 = NULL;
SDL_Surface* text_quote3 = NULL;

TTF_Font *font1 = TTF_OpenFont("fonts/CONTRA__.ttf", 14);
SDL_Color color_black = {0,0,0};
SDL_Color color_white = {255,255,255};

if(font1 != NULL)
{
    text_quote1 = TTF_RenderText_Shaded(font1, m_quote1, color_black, color_white);
    if(m_quote2 != NULL) text_quote2 = TTF_RenderText_Shaded(font1, m_quote2, color_black, color_white);
    if(m_quote3 != NULL) text_quote3 = TTF_RenderText_Shaded(font1, m_quote3, color_black, color_white);
}

apply_surface(origin.x+5,origin.y+5,text_quote1,screen);
if(m_quote2 != NULL) apply_surface(origin.x+5,origin.y+19,text_quote2,screen);
if(m_quote3 != NULL) apply_surface(origin.x+5,origin.y+38,text_quote3,screen);
}
share|improve this answer

There is one more possibility, if performance doesn't matter much, and you are willing to use the newer version SDL2_TTF(available from the official hg.libsdl.org mercurial repository). SDL2_TTF has new functions including TTF_RenderText_Blended_Wrapped, where you can specify a wraplength parameter, which specifies in pixels the maximal width( the surface width also gets padded to this value ). It also supports newline characters, and will break the line at \n characters.

share|improve this answer

Try this, instead of TTF_RenderFont use TTF_RenderText_Blended (just my personal preference) then render the text surface line, then blit onto the main surface and free the text. This way you only have one surface every being rendered on screen.

Heres the method:

void printMessageAt(const string& msg, Uint32 x, Uint32 y, TTF_Font *myFont, SDL_Surface *screen) {
    SDL_Rect dest = {x,y,0,0};
    SDL_Color color = {0, 0, 0, 0};

    // This creates the text surface
    SDL_Surface *stext = TTF_RenderText_Blended(myFont, "The line of text to render", color);

    // If it worked, then blit it to the main screen
    // If you wrap this function
    if (stext) {
        SDL_BlitSurface( stext, NULL, screen, &dest );
        // Very important for performance to free the text surface
        SDL_FreeSurface(stext);
    }
    else {
        throw
        string("Couldn't allocate text surface in printMessageAt");
    }
}

You can call this multiple times for multiple lines of text. Hope this helps, it essentially relies on the special use of the SDL_BlitSurface command.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.